-
check
close
+
{{ Prop.file_name }}
diff --git a/js/animations.js b/js/animations.js
index a2a64dd26..1eb24154d 100644
--- a/js/animations.js
+++ b/js/animations.js
@@ -7,10 +7,8 @@ class Animation {
this.override = false;
this.selected = false;
this.anim_time_update = '';
- this.length = 0
- this.bones = {
- //uuid: BoneAnimator
- }
+ this.length = 0;
+ this.animators = {};
if (typeof data === 'object') {
this.extend(data)
}
@@ -21,28 +19,26 @@ class Animation {
Merge.boolean(this, data, 'override')
Merge.string(this, data, 'anim_time_update')
Merge.number(this, data, 'length')
- if (data.bones) {
- for (var key in data.bones) {
+ if (data.bones && !data.animators) {
+ data.animators = data.bones;
+ }
+ if (data.animators) {
+ for (var key in data.animators) {
var group = Group.all.findInArray( isUUID(key) ? 'uuid' : 'name', key )
if (group) {
var ba = this.getBoneAnimator(group)
- var kfs = data.bones[key]
+ var kfs = data.animators[key]
if (kfs && ba) {
- ba.keyframes.length = 0;
+ ba.rotation.length = 0;
+ ba.position.length = 0;
+ ba.scale.length = 0;
kfs.forEach(kf_data => {
- var kf = new Keyframe(kf_data)
- ba.pushKeyframe(kf)
+ ba.addKeyframe(kf_data/*, kf_data.uuid*/);
})
}
}
}
}
- if (data.particle_effects) {
- this.particle_effects = data.particle_effects;
- }
- if (data.sound_effects) {
- this.sound_effects = data.sound_effects;
- }
return this;
}
getUndoCopy(options) {
@@ -55,19 +51,19 @@ class Animation {
anim_time_update: this.anim_time_update,
length: this.length,
selected: this.selected,
- particle_effects: this.particle_effects,
- sound_effects: this.sound_effects,
+ //particle_effects: this.particle_effects,
+ //sound_effects: this.sound_effects,
}
- if (Object.keys(this.bones).length) {
- copy.bones = {}
- for (var uuid in this.bones) {
- var kfs = this.bones[uuid].keyframes
+ if (Object.keys(this.animators).length) {
+ copy.animators = {}
+ for (var uuid in this.animators) {
+ var kfs = this.animators[uuid].keyframes
if (kfs && kfs.length) {
- if (options && options.bone_names) {
- var group = this.bones[uuid].getGroup();
+ if (options && options.bone_names && this.animators[uuid] instanceof BoneAnimator) {
+ var group = this.animators[uuid].getGroup();
uuid = group ? group.name : ''
}
- var kfs_copy = copy.bones[uuid] = []
+ var kfs_copy = copy.animators[uuid] = []
kfs.forEach(kf => {
kfs_copy.push(kf.getUndoCopy())
})
@@ -82,6 +78,7 @@ class Animation {
Animator.animations.forEach(function(a) {
a.selected = a.playing = false;
})
+ Timeline.vue._data.animators.purge()
Prop.active_panel = 'animations'
this.selected = true;
this.playing = true;
@@ -169,10 +166,10 @@ class Animation {
return;
}
var uuid = group.uuid
- if (!this.bones[uuid]) {
- this.bones[uuid] = new BoneAnimator(uuid, this);
+ if (!this.animators[uuid]) {
+ this.animators[uuid] = new BoneAnimator(uuid, this);
}
- return this.bones[uuid];
+ return this.animators[uuid];
}
add(undo) {
if (undo) {
@@ -204,11 +201,12 @@ class Animation {
getMaxLength() {
var len = this.length||0
- for (var uuid in this.bones) {
- var bone = this.bones[uuid]
+ for (var uuid in this.animators) {
+ var bone = this.animators[uuid]
+ var keyframes = bone.keyframes;
var i = 0;
- while (i < bone.keyframes.length) {
- len = Math.max(len, bone.keyframes[i].time)
+ while (i < keyframes.length) {
+ len = Math.max(len, keyframes[i].time)
i++;
}
}
@@ -242,24 +240,128 @@ class Animation {
delete
*/
])
-class BoneAnimator {
+class GeneralAnimator {
constructor(uuid, animation) {
- this.keyframes = [];
- this.uuid = uuid;
this.animation = animation;
+ this.expanded = false;
+ this.selected = false;
+ this.uuid = uuid || guid();
+ }
+ select() {
+ var scope = this;
+ TickUpdates.keyframes = true;
+ for (var key in Animator.selected.animators) {
+ Animator.selected.animators[key].selected = false;
+ }
+ this.selected = true;
+ Timeline.selected_animator = this;
+ if (!Timeline.vue._data.animators.includes(this)) {
+ Timeline.vue._data.animators.splice(0, 0, this);
+ }
+ Vue.nextTick(() => {
+ scope.scrollTo();
+ })
+ if (!this.expanded) this.expanded = true;
+ return this;
+ }
+ addKeyframe(data, uuid) {
+ var channel = data.channel;
+ if (channel && this[channel]) {
+ var kf = new Keyframe(data, uuid);
+ this[channel].push(kf);
+ kf.animator = this;
+ return kf;
+ }
+ }
+ createKeyframe(value, time, channel, undo) {
+ if (!this[channel]) return;
+ var keyframes = [];
+ if (undo) {
+ Undo.initEdit({keyframes})
+ }
+ var keyframe = new Keyframe({
+ channel: channel,
+ time: time
+ });
+ keyframes.push(keyframe);
+
+ if (this.fillValues) {
+ this.fillValues(keyframe, value);
+ } else if (value) {
+ keyframe.extend(value);
+ }
+ keyframe.channel = channel;
+ keyframe.time = time;
+
+ this[channel].push(keyframe);
+ keyframe.animator = this;
+ keyframe.select();
+ TickUpdates.keyframes = true;
+
+ var deleted = [];
+ delete keyframe.time_before;
+ keyframe.replaceOthers(deleted);
+ Undo.addKeyframeCasualties(deleted);
+
+ if (undo) {
+ Undo.finishEdit('added keyframe')
+ }
+ return keyframe;
+ }
+ scrollTo() {
+ var el = $(`#timeline_body_inner > li[uuid=${this.uuid}]`).get(0)
+ if (el) {
+ var offset = el.offsetTop;
+ var timeline = $('#timeline_body').scrollTop();
+ var height = $('#timeline_body').height();
+ if (offset < timeline) {
+ $('#timeline_body').animate({
+ scrollTop: offset
+ }, 200);
+ }
+ if (offset + el.clientHeight > timeline + height) {
+ $('#timeline_body').animate({
+ scrollTop: offset - (height-el.clientHeight-20)
+ }, 200);
+ }
+ }
+ }
+}
+class BoneAnimator extends GeneralAnimator {
+ constructor(uuid, animation) {
+ super(uuid, animation);
+ this.uuid = uuid;
+
+ this.rotation = [];
+ this.position = [];
+ this.scale = [];
+ }
+ get name() {
+ var group = this.getGroup();
+ if (group) return group.name;
+ return '';
+ }
+ get keyframes() {
+ return [...this.rotation, ...this.position, ...this.scale];
}
getGroup() {
this.group = Group.all.findInArray('uuid', this.uuid)
if (!this.group) {
console.log('no group found for '+this.uuid)
- if (this.animation && this.animation.bones[this.uuid]) {
+ if (this.animation && this.animation.animators[this.uuid] && this.animation.animators[this.uuid].type == 'bone') {
delete this.animation.bones[this.uuid];
}
}
return this.group
}
- select() {
+ select(group_is_selected) {
var duplicates;
+ for (var key in this.animation.animators) {
+ this.animation.animators[key].selected = false;
+ }
+ if (group_is_selected !== true) {
+ this.group.select();
+ }
function iterate(arr) {
arr.forEach((it) => {
if (it.type === 'group' && !duplicates) {
@@ -279,34 +381,28 @@ class BoneAnimator {
buttons: ['dialog.ok'],
})
}
- Timeline.animator = this;
+ super.select();
- if (Timeline.keyframes !== this.keyframes) {
- Timeline.keyframes.forEach(function(kf) {
- kf.selected = false
+ if (this[Toolbox.selected.animation_channel] && (Timeline.selected.length == 0 || Timeline.selected[0].animator != this)) {
+ var nearest;
+ this[Toolbox.selected.animation_channel].forEach(kf => {
+ if (Math.abs(kf.time - Timeline.time) < 0.002) {
+ nearest = kf;
+ }
})
- Timeline.selected.length = 0
- Timeline.keyframes = Timeline.vue._data.keyframes = this.keyframes
- if (this.keyframes[0]) {
- this.keyframes[0].select()
- } else {
- updateKeyframeSelection()
+ if (nearest) {
+ nearest.select();
}
- } else {
- updateKeyframeSelection()
}
+
if (this.group && this.group.parent && this.group.parent !== 'root') {
this.group.parent.openUp()
}
- TickUpdates.keyframes = true;
return this;
}
- addKeyframe(values, time, channel) {
- var keyframe = new Keyframe({
- time: time,
- channel: channel
- })
- if (values && typeof values === 'object') {
+ fillValues(keyframe, values) {
+
+ if (values instanceof Array) {
keyframe.extend({
x: values[0],
y: values[1],
@@ -321,8 +417,8 @@ class BoneAnimator {
y: values,
z: values
})
- } else {
- var ref = this.interpolate(time, channel, true)
+ } else if (values == null) {
+ var ref = this.interpolate(Timeline.time, keyframe.channel, true)
if (ref) {
let e = 1e2
ref.forEach((r, i) => {
@@ -338,15 +434,13 @@ class BoneAnimator {
isQuaternion: ref.length === 4
})
}
+ } else {
+ keyframe.extend(values)
}
- this.keyframes.push(keyframe)
- keyframe.parent = this;
- TickUpdates.keyframes = true;
- return keyframe;
}
pushKeyframe(keyframe) {
- this.keyframes.push(keyframe)
- keyframe.parent = this;
+ this[keyframe.channel].push(keyframe)
+ keyframe.animator = this;
return this;
}
doRender() {
@@ -394,12 +488,9 @@ class BoneAnimator {
var before = false
var after = false
var result = false
- while (i < this.keyframes.length) {
- var keyframe = this.keyframes[i]
-
- if (keyframe.channel !== channel) {
- } else if (keyframe.time < time) {
+ for (var keyframe of this[channel]) {
+ if (keyframe.time < time) {
if (!before || keyframe.time > before.time) {
before = keyframe
}
@@ -457,11 +548,48 @@ class BoneAnimator {
this.displayScale(this.interpolate(time, 'scale'))
}
}
+ BoneAnimator.prototype.channels = ['rotation', 'position', 'scale']
+class EffectAnimator extends GeneralAnimator {
+ constructor(animation) {
+ super(null, animation);
+
+ this.name = tl('timeline.effects')
+ this.selected = false;
+
+ this.particle = [];
+ this.sound = [];
+ }
+ get keyframes() {
+ return [...this.particle, ...this.sound];
+ }
+ pushKeyframe(keyframe) {
+ this[keyframe.channel].push(keyframe)
+ keyframe.animator = this;
+ return this;
+ }
+ displayFrame() {
+ this.sound.forEach(kf => {
+ var diff = kf.time - Timeline.time;
+ if (diff >= 0 && diff < (1/60) * (Timeline.playback_speed/100)) {
+ if (kf.file && !kf.cooldown) {
+ var media = new Audio(kf.file);
+ media.volume = Math.clamp(settings.volume.value/100, 0, 1);
+ media.play();
+
+ kf.cooldown = true;
+ setTimeout(() => {
+ delete kf.cooldown;
+ }, 400)
+ }
+ }
+ })
+ }
+}
+ EffectAnimator.prototype.channels = ['particle', 'sound']
class Keyframe {
constructor(data, uuid) {
this.type = 'keyframe'
- this.channel = 'rotation'//, 'position', 'scale'
- this.channel_index = 0;
+ this.channel = 'rotation';
this.time = 0;
this.selected = 0;
this.x = '0';
@@ -469,17 +597,49 @@ class Keyframe {
this.z = '0';
this.w = '0';
this.isQuaternion = false;
+ this.effect = '';
+ this.file = '';
+ this.locator = '';
this.uuid = (uuid && isUUID(uuid)) ? uuid : guid();
if (typeof data === 'object') {
+ Merge.string(this, data, 'channel')
+ this.transform = this.channel === 'rotation' || this.channel === 'position' || this.channel === 'scale';
this.extend(data)
if (this.channel === 'scale' && data.x == undefined && data.y == undefined && data.z == undefined) {
this.x = this.y = this.z = 1;
}
}
}
+ extend(data) {
+ Merge.number(this, data, 'time')
+
+ if (this.transform) {
+ if (data.values != undefined) {
+ if (typeof data.values == 'number' || typeof data.values == 'string') {
+ data.x = data.y = data.z = data.values;
+
+ } else if (data.values instanceof Array) {
+ data.x = data.values[0];
+ data.y = data.values[1];
+ data.z = data.values[2];
+ data.w = data.values[3];
+ }
+ }
+ Merge.string(this, data, 'x')
+ Merge.string(this, data, 'y')
+ Merge.string(this, data, 'z')
+ Merge.string(this, data, 'w')
+ Merge.boolean(this, data, 'isQuaternion')
+ } else {
+ Merge.string(this, data, 'effect')
+ Merge.string(this, data, 'locator')
+ Merge.string(this, data, 'file')
+ }
+ return this;
+ }
get(axis) {
if (!this[axis]) {
- return 0;
+ return this.transform ? 0 : '';
} else if (!isNaN(this[axis])) {
return parseFloat(this[axis])
} else {
@@ -490,15 +650,14 @@ class Keyframe {
return Molang.parse(this[axis])
}
set(axis, value) {
- if (axis === 'x' || axis === 'y' || axis === 'z' || axis === 'w') {
- this[axis] = value
- }
+ this[axis] = value;
return this;
}
offset(axis, amount) {
var value = this.get(axis)
if (!value || value === '0') {
this.set(axis, amount)
+ return amount;
}
if (typeof value === 'number') {
this.set(axis, value+amount)
@@ -521,6 +680,36 @@ class Keyframe {
this.set(axis, value)
return value;
}
+ flip(axis) {
+ if (!this.transform) return this;
+ function negate(value) {
+ if (!value || value === '0') {
+ return value;
+ }
+ if (typeof value === 'number') {
+ return -value;
+ }
+ var start = value.match(/^-?\s*\d*(\.\d+)?\s*(\+|-)/)
+ if (start) {
+ var number = parseFloat( start[0].substr(0, start[0].length-1) );
+ return trimFloatNumber(-number) + value.substr(start[0].length-1);
+ } else {
+ return `-(${value})`;
+ }
+ }
+ if (this.channel == 'rotation') {
+ for (var i = 0; i < 3; i++) {
+ if (i != axis) {
+ let l = getAxisLetter(i)
+ this.set(l, negate(this.get(l)))
+ }
+ }
+ } else if (this.channel == 'position' || this.channel == 'scale') {
+ let l = getAxisLetter(axis)
+ this.set(l, negate(this.get(l)))
+ }
+ return this;
+ }
getLerp(other, axis, amount, allow_expression) {
if (allow_expression && this.get(axis) === other.get(axis)) {
return this.get(axis)
@@ -540,13 +729,24 @@ class Keyframe {
}
return arr;
}
+ replaceOthers(save) {
+ var scope = this;
+ var arr = this.animator[this.channel];
+ var replaced;
+ arr.forEach(kf => {
+ if (kf != scope && Math.abs(kf.time - scope.time) < 0.0001) {
+ save.push(kf);
+ kf.remove();
+ }
+ })
+ }
select(event) {
var scope = this;
- if (this.dragging) {
- delete this.dragging
+ if (Timeline.dragging_keyframes) {
+ Timeline.dragging_keyframes = false
return this;
}
- if (!event || (!event.shiftKey && !event.ctrlKey)) {
+ if (!event || (!event.shiftKey && !event.ctrlOrCmd)) {
Timeline.selected.forEach(function(kf) {
kf.selected = false
})
@@ -554,9 +754,10 @@ class Keyframe {
}
if (event && event.shiftKey && Timeline.selected.length) {
var last = Timeline.selected[Timeline.selected.length-1]
- if (last && last.channel === scope.channel) {
+ if (last && last.channel === scope.channel && last.animator == scope.animator) {
Timeline.keyframes.forEach((kf) => {
if (kf.channel === scope.channel &&
+ kf.animator === scope.animator &&
Math.isBetween(kf.time, last.time, scope.time) &&
!kf.selected
) {
@@ -566,9 +767,11 @@ class Keyframe {
})
}
}
- if (Timeline.selected.indexOf(this) == -1) {
- Timeline.selected.push(this)
+ Timeline.selected.safePush(this);
+ if (Timeline.selected.length == 1 && Timeline.selected[0].animator.selected == false) {
+ Timeline.selected[0].animator.select()
}
+
var select_tool = true;
Timeline.selected.forEach(kf => {
if (kf.channel != scope.channel) select_tool = false;
@@ -576,10 +779,10 @@ class Keyframe {
this.selected = true
updateKeyframeSelection()
if (select_tool) {
- switch (this.channel_index) {
- case 0: BarItems.rotate_tool.select(); break;
- case 1: BarItems.move_tool.select(); break;
- case 2: BarItems.resize_tool.select(); break;
+ switch (this.channel) {
+ case 'rotation': BarItems.rotate_tool.select(); break;
+ case 'position': BarItems.move_tool.select(); break;
+ case 'scale': BarItems.resize_tool.select(); break;
}
}
return this;
@@ -589,47 +792,6 @@ class Keyframe {
Animator.preview()
return this;
}
- findNearest(distance, channel, direction) {
- if (!this.parent) return [];
- //channel: all, others, this, 0, 1, 2
- //direction: true>, false<, undefined<>
- var scope = this
- function getDelta(kf, abs) {
- if (abs) {
- return Math.abs(kf.time - scope.time)
- } else {
- return kf.time - scope.time
- }
- }
- var matches = []
- var i = 0;
- while (i < scope.parent.keyframes.length) {
- var kf = scope.parent.keyframes[i]
- let delta = getDelta(kf)
-
- let delta_match = Math.abs(delta) <= distance &&
- (delta>0 == direction || direction === undefined)
-
- let channel_match = (
- (channel === 'all') ||
- (channel === 'others' && kf.channel !== scope.channel) ||
- (channel === 'this' && kf.channel === scope.channel) ||
- (channel === kf.channel_index) ||
- (channel === kf.channel)
- )
-
- if (channel_match && delta_match) {
- matches.push(kf)
- }
- i++;
- }
-
- matches.sort((a, b) => {
- return getDelta(a, true) - getDelta(b, true)
- })
-
- return matches
- }
showContextMenu(event) {
if (!this.selected) {
this.select();
@@ -638,38 +800,33 @@ class Keyframe {
return this;
}
remove() {
- if (this.parent) {
- this.parent.keyframes.remove(this)
+ if (this.animator) {
+ this.animator[this.channel].remove(this)
}
Timeline.selected.remove(this)
}
- extend(data) {
- if (data.channel && Animator.possible_channels[data.channel]) {
- Merge.string(this, data, 'channel')
- } else if (typeof data.channel === 'number') {
- this.channel = Animator.channel_index[data.channel]
- }
- Merge.number(this, data, 'time')
-
- Merge.string(this, data, 'x')
- Merge.string(this, data, 'y')
- Merge.string(this, data, 'z')
- Merge.string(this, data, 'w')
- Merge.boolean(this, data, 'isQuaternion')
-
- this.channel_index = Animator.channel_index.indexOf(this.channel)
- return this;
- }
getUndoCopy() {
var copy = {
- channel: this.channel_index,
+ animator: this.animator && this.animator.uuid,
+ channel: this.channel,
time: this.time,
x: this.x,
y: this.y,
z: this.z,
}
- if (this.channel_index === 0 && this.isQuaternion) {
- copy.w = this.w
+ if (this.transform) {
+ copy.x = this.x;
+ copy.y = this.y;
+ copy.z = this.z;
+ if (this.channel == 'rotation' && this.isQuaternion) {
+ copy.w = this.w
+ }
+ } else {
+ copy.effect = this.effect;
+ if (this.channel == 'particle') {
+ copy.locator = this.locator;
+ }
+ if (this.file) copy.file = this.file;
}
return copy;
}
@@ -687,33 +844,23 @@ class Keyframe {
updateKeyframeSelection()
}
},
+ 'change_keyframe_file',
+ '_',
'copy',
'delete',
- /*{name: 'generic.delete', icon: 'delete', click: function(keyframe) {
- keyframe.select({shiftKey: true})
- removeSelectedKeyframes()
- }},*/
- /*
- settotimestamp
- delete
- */
])
function updateKeyframeValue(obj) {
- var axis = $(obj).attr('axis')
- var value = $(obj).val()
+ var axis = $(obj).attr('axis');
+ var value = $(obj).val();
Timeline.selected.forEach(function(kf) {
- kf.set(axis, value)
+ kf.set(axis, value);
})
- BARS.updateConditions()
- Animator.preview()
+ if (!['effect', 'locator'].includes(axis)) {
+ Animator.preview();
+ }
}
function updateKeyframeSelection() {
- if (!Group.selected) {
- Timeline.keyframes = Timeline.vue._data.keyframes = []
- Timeline.animator = undefined
- Timeline.selected.length = 0
- }
var multi_channel = false;
var channel = false;
Timeline.selected.forEach((kf) => {
@@ -723,28 +870,37 @@ function updateKeyframeSelection() {
multi_channel = true
}
})
+ $('.panel#keyframe .bar').hide();
+
if (Timeline.selected.length && !multi_channel) {
var first = Timeline.selected[0]
+
$('#keyframe_type_label').text(tl('panel.keyframe.type', [tl('timeline.'+first.channel)] ))
- $('#keyframe_bar_x, #keyframe_bar_y, #keyframe_bar_z').show()
- $('#keyframe_bar_w').toggle(first.channel === 'rotation' && first.isQuaternion)
-
- var values = [
- first.get('x'),
- first.get('y'),
- first.get('z'),
- first.get('w')
- ]
- values.forEach((v, vi) => {
- if (typeof v === 'number') {
- values[vi] = trimFloatNumber(v)
+
+ if (first.animator instanceof BoneAnimator) {
+ function _gt(axis) {
+ var n = first.get(axis);
+ if (typeof n == 'number') return trimFloatNumber(n);
+ return n;
}
- })
- $('#keyframe_bar_x input').val(values[0])
- $('#keyframe_bar_y input').val(values[1])
- $('#keyframe_bar_z input').val(values[2])
- $('#keyframe_bar_w input').val(values[3])
+ $('#keyframe_bar_x, #keyframe_bar_y, #keyframe_bar_z').show();
+ $('#keyframe_bar_w').toggle(first.channel === 'rotation' && first.isQuaternion)
+ $('#keyframe_bar_x input').val(_gt('x'));
+ $('#keyframe_bar_y input').val(_gt('y'));
+ $('#keyframe_bar_z input').val(_gt('z'));
+ if (first.channel === 'rotation' && first.isQuaternion) {
+ $('#keyframe_bar_w input').val(_gt('w'));
+ }
+ } else {
+ $('#keyframe_bar_effect').show();
+ $('#keyframe_bar_effect input').val(first.get('effect'));
+ if (first.channel == 'particle') {
+ $('#keyframe_bar_locator').show();
+ $('#keyframe_bar_locator input').val(first.get('locator'));
+ }
+
+ }
BarItems.slider_keyframe_time.update()
} else {
$('#keyframe_type_label').text('')
@@ -779,7 +935,6 @@ function removeSelectedKeyframes() {
Animator.preview()
Undo.finishEdit('remove keyframes')
}
-
function findBedrockAnimation() {
var animation_path = ModelMeta.export_path.split(osfs)
var index = animation_path.lastIndexOf('models')
@@ -796,9 +951,9 @@ function findBedrockAnimation() {
})
}
}
+
const Animator = {
- possible_channels: {rotation: true, position: true, scale: true},
- channel_index: ['rotation', 'position', 'scale'],
+ possible_channels: {rotation: true, position: true, scale: true, sound: true, particle: true},
open: false,
animations: [],
frame: 0,
@@ -839,7 +994,7 @@ const Animator = {
findBedrockAnimation()
}
},
- leave (argument) {
+ leave() {
Timeline.pause()
Animator.open = false;
$('body').removeClass('animation_mode')
@@ -860,11 +1015,20 @@ const Animator = {
Animator.animations.forEach(animation => {
if (animation.playing) {
- animation.getBoneAnimator(group).displayFrame(Timeline.second)
+ animation.getBoneAnimator(group).displayFrame(Timeline.time)
}
})
group.mesh.updateMatrixWorld()
})
+
+ Animator.animations.forEach(animation => {
+ if (animation.playing) {
+ if (animation.animators.effects) {
+ animation.animators.effects.displayFrame();
+ }
+ }
+ })
+
if (Group.selected) {
Transformer.center()
}
@@ -883,30 +1047,69 @@ const Animator = {
anim_time_update: a.anim_time_update,
length: a.animation_length,
blend_weight: a.blend_weight,
- particle_effects: a.particle_effects,
- sound_effects: a.sound_effects,
+ //particle_effects: a.particle_effects,
+ //sound_effects: a.sound_effects,
}).add()
//Bones
- for (var bone_name in a.bones) {
- var b = a.bones[bone_name]
- var group = Group.all.findInArray('name', bone_name)
- if (group) {
- var ba = new BoneAnimator(group.uuid, animation);
- animation.bones[group.uuid] = ba;
- //Channels
- for (var channel in b) {
- if (Animator.possible_channels[channel]) {
- if (typeof b[channel] === 'string' || typeof b[channel] === 'number' || (typeof b[channel] === 'object' && b[channel].constructor.name === 'Array')) {
- ba.addKeyframe(b[channel], 0, channel)
- } else if (typeof b[channel] === 'object') {
- for (var timestamp in b[channel]) {
- ba.addKeyframe(b[channel][timestamp], parseFloat(timestamp), channel)
+ if (a.bones) {
+ for (var bone_name in a.bones) {
+ var b = a.bones[bone_name]
+ var group = Group.all.findInArray('name', bone_name)
+ if (group) {
+ var ba = new BoneAnimator(group.uuid, animation);
+ animation.animators[group.uuid] = ba;
+ //Channels
+ for (var channel in b) {
+ if (Animator.possible_channels[channel]) {
+ if (typeof b[channel] === 'string' || typeof b[channel] === 'number' || b[channel] instanceof Array) {
+ ba.addKeyframe({
+ time: 0,
+ channel,
+ values: b[channel],
+ })
+ } else if (typeof b[channel] === 'object') {
+ for (var timestamp in b[channel]) {
+ ba.addKeyframe({
+ time: parseFloat(timestamp),
+ channel,
+ values: b[channel][timestamp],
+ });
+ }
}
}
}
}
}
}
+ if (a.sound_effects) {
+ if (!animation.animators.effects) {
+ animation.animators.effects = new EffectAnimator(animation);
+ }
+ for (var timestamp in a.sound_effects) {
+ var sound = a.sound_effects[timestamp];
+ if (sound instanceof Array) sound = sound[0];
+ animation.animators.effects.addKeyframe({
+ channel: 'sound',
+ time: parseFloat(timestamp),
+ effect: sound.effect,
+ })
+ }
+ }
+ if (a.particle_effects) {
+ if (!animation.animators.effects) {
+ animation.animators.effects = new EffectAnimator(animation);
+ }
+ for (var timestamp in a.particle_effects) {
+ var particle = a.particle_effects[timestamp];
+ if (particle instanceof Array) particle = particle[0];
+ animation.animators.effects.addKeyframe({
+ channel: 'particle',
+ time: parseFloat(timestamp),
+ effect: particle.effect,
+ locator: particle.locator,
+ })
+ }
+ }
if (!Animator.selected) {
animation.select()
}
@@ -928,16 +1131,42 @@ const Animator = {
if (a.override) ani_tag.override_previous_animation = true
if (a.anim_time_update) ani_tag.anim_time_update = a.anim_time_update
ani_tag.bones = {}
- if (a.particle_effects) ani_tag.particle_effects = a.particle_effects;
- if (a.sound_effects) ani_tag.sound_effects = a.sound_effects;
- for (var uuid in a.bones) {
- var group = a.bones[uuid].getGroup()
- if (group && a.bones[uuid].keyframes.length) {
+ //if (a.particle_effects) ani_tag.particle_effects = a.particle_effects;
+ //if (a.sound_effects) ani_tag.sound_effects = a.sound_effects;
+
+ for (var uuid in a.animators) {
+ var animator = a.animators[uuid];
+ if (animator instanceof EffectAnimator) {
+ animator.sound.forEach(kf => {
+ if (!ani_tag.sound_effects) ani_tag.sound_effects = {};
+ let timecode = Math.clamp(trimFloatNumber(Math.round(kf.time*60)/60), 0) + '';
+ if (!timecode.includes('.')) {
+ timecode += '.0';
+ }
+ ani_tag.sound_effects[timecode] = {
+ effect: kf.effect
+ }
+ })
+ animator.particle.forEach(kf => {
+ if (!ani_tag.particle_effects) ani_tag.particle_effects = {};
+ let timecode = Math.clamp(trimFloatNumber(Math.round(kf.time*60)/60), 0) + '';
+ if (!timecode.includes('.')) {
+ timecode += '.0';
+ }
+ ani_tag.particle_effects[timecode] = {
+ effect: kf.effect,
+ locator: kf.locator || undefined
+ }
+ })
+
+ } else if (a.animators[uuid].keyframes.length && a.animators[uuid].group) {
+
+ var group = a.animators[uuid].group;
var bone_tag = ani_tag.bones[group.name] = {}
var channels = {}
//Saving Keyframes
- a.bones[uuid].keyframes.forEach(function(kf) {
+ a.animators[uuid].keyframes.forEach(function(kf) {
if (!channels[kf.channel]) {
channels[kf.channel] = {}
}
@@ -965,6 +1194,9 @@ const Animator = {
}
}
}
+ if (Object.keys(ani_tag.bones).length == 0) {
+ delete ani_tag.bones;
+ }
})
return {
format_version: '1.8.0',
@@ -973,24 +1205,116 @@ const Animator = {
}
}
const Timeline = {
- keyframes: [],//frames
+ animators: [],
selected: [],//frames
playback_speed: 100,
- second: 0,
+ time: 0,
+ get second() {return Timeline.time},
playing: false,
+ selector: {
+ start: [0, 0],
+ selecting: false,
+ selected_before: [],
+ down(e) {
+ if (e.which !== 1 || (
+ !e.target.classList.contains('keyframe_section') &&
+ !e.target.classList.contains('animator_head_bar') &&
+ e.target.id !== 'timeline_body_inner'
+ )) {
+ return
+ };
+ var offset = $('#timeline_body_inner').offset();
+ var R = Timeline.selector;
+ R.panel_offset = [
+ offset.left,
+ offset.top,
+ ]
+ R.start = [
+ e.clientX - R.panel_offset[0],
+ e.clientY - R.panel_offset[1],
+ ]
+ if (e.shiftKey) {
+ Timeline.selector.selected_before = Timeline.selected.slice();
+ }
+ R.selecting = true;
+ $('#timeline_selector').show()
+ Timeline.selector.move(e)
+ },
+ move(e) {
+ var R = Timeline.selector;
+ if (!R.selecting) return;
+ //CSS
+ var offset = $('#timeline_body_inner').offset();
+ R.panel_offset = [
+ offset.left,
+ offset.top,
+ ]
+ var rect = getRectangle(R.start[0], R.start[1], e.clientX - R.panel_offset[0], e.clientY - R.panel_offset[1])
+ $('#timeline_selector')
+ .css('width', rect.x + 'px')
+ .css('height', rect.y + 'px')
+ .css('left', rect.ax + 'px')
+ .css('top', rect.ay + 'px');
+ //Keyframes
+ var epsilon = 6;
+ var focus = Timeline.vue._data.focus_channel;
+ rect.ax -= epsilon;
+ rect.ay -= epsilon;
+ rect.bx += epsilon;
+ rect.by += epsilon;
+
+ var min_time = (rect.ax-Timeline.vue._data.head_width-8)/Timeline.vue._data.size;
+ var max_time = (rect.bx-Timeline.vue._data.head_width-8)/Timeline.vue._data.size;
+
+ Timeline.selected.length = 0;
+ for (var animator of Timeline.animators) {
+ var node = $('#timeline_body_inner .animator[uuid=' + animator.uuid + ']').get(0)
+ var offset = node && node.offsetTop;
+ for (var kf of animator.keyframes) {
+ if (Timeline.selector.selected_before.includes(kf)) {
+ Timeline.selected.push(kf);
+ continue;
+ }
+ kf.selected = false;
+ if (kf.time > min_time &&
+ kf.time < max_time &&
+ (kf.channel == focus || !focus)
+ ) {
+ var channel_index = focus ? 0 : animator.channels.indexOf(kf.channel);
+ height = offset + channel_index*24 + 36;
+ if (height > rect.ay && height < rect.by) {
+ kf.selected = true;
+ Timeline.selected.push(kf);
+ }
+ }
+ }
+ }
+
+ },
+ end(e) {
+ if (!Timeline.selector.selecting) return false;
+ e.stopPropagation()
+ Timeline.selector.selected_before.empty();
+ Timeline.selector.selecting = false;
+ $('#timeline_selector')
+ .css('width', 0)
+ .css('height', 0)
+ .hide()
+ },
+ },
setTime(seconds, editing) {
seconds = limitNumber(seconds, 0, 1000)
Timeline.vue._data.marker = seconds
- Timeline.second = seconds
+ Timeline.time = seconds
if (!editing) {
Timeline.setTimecode(seconds)
}
Timeline.updateSize()
//Scroll
- var scroll = $('#timeline_inner').scrollLeft()
- var marker = Timeline.second * Timeline.vue._data.size + 8
- if (marker < scroll || marker > scroll + $('#timeline_inner').width()) {
- $('#timeline_inner').scrollLeft(marker-16)
+ var scroll = $('#timeline_body').scrollLeft()
+ var marker = Timeline.time * Timeline.vue._data.size + 8
+ if (marker < scroll || marker > scroll + $('#timeline_body').width() - Timeline.vue._data.head_width) {
+ $('#timeline_body').scrollLeft(marker-16)
}
},
setTimecode(time) {
@@ -1004,24 +1328,32 @@ const Timeline = {
snapTime(time) {
//return time;
if (time == undefined || isNaN(time)) {
- time = Timeline.second;
+ time = Timeline.time;
}
var fps = Math.clamp(settings.animation_snap.value, 1, 120);
return Math.clamp(Math.round(time*fps)/fps, 0);
},
setup() {
- $('#timeline_inner #timeline_time').mousedown(e => {
+ $('#timeline_body')
+ .mousedown(Timeline.selector.down)
+ .mousemove(Timeline.selector.move)
+ $(document).mouseup(Timeline.selector.end);
+
+
+ $('#timeline_time').mousedown(e => {
Timeline.dragging_marker = true;
- let time = e.offsetX / Timeline.vue._data.size
+ let time = (e.offsetX) / Timeline.vue._data.size
Timeline.setTime(Timeline.snapTime(time))
Animator.preview()
})
$(document).mousemove(e => {
if (Timeline.dragging_marker) {
- let offset = mouse_pos.x - $('#timeline_inner #timeline_time').offset().left
- let time = offset / Timeline.vue._data.size
- Timeline.setTime(Timeline.snapTime(time))
- Animator.preview()
+ let offset = mouse_pos.x - $('#timeline_time').offset().left;
+ let time = Timeline.snapTime(offset / Timeline.vue._data.size)
+ if (Timeline.time != time) {
+ Timeline.setTime(time)
+ Animator.preview()
+ }
}
})
.mouseup(e => {
@@ -1066,7 +1398,7 @@ const Timeline = {
.on('focusout keydown', e => {
if (e.type === 'focusout' || Keybinds.extra.confirm.keybind.isTriggered(e) || Keybinds.extra.cancel.keybind.isTriggered(e)) {
$('#timeline_corner').attr('contenteditable', false)
- Timeline.setTimecode(Timeline.second)
+ Timeline.setTimecode(Timeline.time)
}
})
.on('keyup', e => {
@@ -1084,25 +1416,31 @@ const Timeline = {
= times[0]*60
+ limitNumber(times[1], 0, 59)
+ limitNumber(times[2]/100, 0, 99)
- if (Math.abs(seconds-Timeline.second) > 1e-3 ) {
+ if (Math.abs(seconds-Timeline.time) > 1e-3 ) {
Timeline.setTime(seconds, true)
Animator.preview()
}
})
- $('#timeline_inner').on('mousewheel', function() {
- if (event.ctrlKey) {
+ $('#timeline_vue').on('mousewheel', function() {
+ var body = $('#timeline_body').get(0)
+ if (event.shiftKey) {
+ body.scrollLeft += event.deltaY/4
+ } else if (event.ctrlOrCmd) {
var offset = 1 - event.deltaY/600
Timeline.vue._data.size = limitNumber(Timeline.vue._data.size * offset, 10, 1000)
- this.scrollLeft *= offset
- let l = (event.offsetX / this.clientWidth) * 500 * (event.deltaY<0?1:-0.2)
- this.scrollLeft += l
+ body.scrollLeft *= offset
+ let l = (event.offsetX / body.clientWidth) * 500 * (event.deltaY<0?1:-0.2)
+ body.scrollLeft += l
} else {
- this.scrollLeft += event.deltaY/2
+ body.scrollTop += event.deltaY/6.25
}
Timeline.updateSize()
event.preventDefault();
});
+ $('#timeline_body').on('scroll', e => {
+ Timeline.vue._data.scroll_left = $('#timeline_body').scrollLeft()||0;
+ })
BarItems.slider_animation_speed.update()
Timeline.is_setup = true
@@ -1110,26 +1448,27 @@ const Timeline = {
},
update() {
//Draggable
- $('#timeline_inner .keyframe:not(.ui-draggable)').draggable({
+ $('#timeline_body .keyframe:not(.ui-draggable)').draggable({
axis: 'x',
distance: 4,
helper: () => $('
'),
start: function(event, ui) {
- Undo.initEdit({keyframes: Timeline.keyframes, keep_saved: true})
- var id = $(ui.helper).attr('id')
- var clicked = Timeline.vue._data.keyframes.findInArray('uuid', id)
- if (clicked && !clicked.selected) {
+ var id = $(event.target).attr('id');
+ var clicked = Timeline.keyframes.findInArray('uuid', id)
+
+ if (!$(event.target).hasClass('selected') && !event.shiftKey && Timeline.selected.length != 0) {
clicked.select()
+ } else if (clicked && !clicked.selected) {
+ clicked.select({shiftKey: true})
}
- clicked.dragging = true;
+
+ Undo.initEdit({keyframes: Timeline.selected, keep_saved: true})
+ Timeline.dragging_keyframes = true;
var i = 0;
- for (var i = 0; i < Timeline.vue._data.keyframes.length; i++) {
- var kf = Timeline.vue._data.keyframes[i]
- if (kf.selected) {
- kf.time_before = kf.time
- }
+ for (var kf of Timeline.selected) {
+ kf.time_before = kf.time
}
},
drag: function(event, ui) {
@@ -1137,48 +1476,35 @@ const Timeline = {
var id = $(ui.helper).attr('id')
var snap_value = false
var nearest
- var i = 0;
- while (i < Timeline.vue._data.keyframes.length) {
- var kf = Timeline.vue._data.keyframes[i]
- if (kf.uuid === id) {
- i = Infinity
- kf.time = Timeline.snapTime(limitNumber(kf.time_before + difference, 0, 256))
- nearest = kf.findNearest(8 / Timeline.vue._data.size, 'others')
- }
- i++;
- }
- if (nearest && nearest.length) {
- snap_value = nearest[0].time
- difference = snap_value - kf.time_before;
- }
- var i = 0;
- while (i < Timeline.vue._data.keyframes.length) {
- var kf = Timeline.vue._data.keyframes[i]
- if (kf.uuid === id || kf.selected) {
- var t = limitNumber(kf.time_before + difference, 0, 256)
- if (kf.uuid === id) {
- ui.position.left = t * Timeline.vue._data.size + 8
- }
- kf.time = Timeline.snapTime(t)
+ for (var kf of Timeline.selected) {
+ var t = limitNumber(kf.time_before + difference, 0, 256)
+ if (kf.uuid === id) {
+ ui.position.left = t * Timeline.vue._data.size + 8
}
- i++;
+ kf.time = Timeline.snapTime(t);
}
BarItems.slider_keyframe_time.update()
Animator.preview()
},
stop: function(event, ui) {
+ var deleted = []
+ for (var kf of Timeline.selected) {
+ delete kf.time_before;
+ kf.replaceOthers(deleted);
+ }
+ Undo.addKeyframeCasualties(deleted);
Undo.finishEdit('drag keyframes')
}
})
},
updateSize() {
let size = Timeline.vue._data.size
- var max_length = ($('#timeline_inner').width()-8) / Timeline.vue._data.size;
- Timeline.vue._data.keyframes.forEach((kf) => {
+ var max_length = ($('#timeline_body').width()-8) / Timeline.vue._data.size;
+ Timeline.keyframes.forEach((kf) => {
max_length = Math.max(max_length, kf.time)
})
- max_length = Math.max(max_length, Timeline.second)
+ max_length = Math.max(max_length, Timeline.time) + 50/Timeline.vue._data.size
Timeline.vue._data.length = max_length
Timeline.vue._data.timecodes.length = 0
@@ -1207,6 +1533,10 @@ const Timeline = {
i += step;
}
},
+ updateScroll(e) {
+ $('.channel_head').css('left', scroll_amount+'px')
+ $('#timeline_time').css('left', -scroll_amount+'px')
+ },
unselect(e) {
if (!Animator.selected) return;
Timeline.keyframes.forEach((kf) => {
@@ -1227,10 +1557,10 @@ const Timeline = {
},
loop() {
Animator.preview()
- if (Animator.selected && Timeline.second < (Animator.selected.length||1e3)) {
+ if (Animator.selected && Timeline.time < (Animator.selected.length||1e3)) {
Animator.interval = setTimeout(Timeline.loop, 100/6)
- Timeline.setTime(Timeline.second + (1/60) * (Timeline.playback_speed/100))
+ Timeline.setTime(Timeline.time + (1/60) * (Timeline.playback_speed/100))
} else {
Timeline.setTime(0)
if (Animator.selected && Animator.selected.loop) {
@@ -1248,6 +1578,7 @@ const Timeline = {
Animator.interval = false
}
},
+ /*
addKeyframe(channel, reset) {
if (!Animator.selected) {
Blockbench.showQuickMessage('message.no_animation_selected')
@@ -1264,44 +1595,35 @@ const Timeline = {
kf.select()
Animator.preview()
Undo.finishEdit('add keyframe')
+ },*/
+ get keyframes() {
+ var keyframes = [];
+ Timeline.vue._data.animators.forEach(animator => {
+ keyframes = [...keyframes, ...animator.keyframes]
+ })
+ return keyframes;
},
showMenu(event) {
- if (event.target.id === 'timeline_inner') {
+ if (event.target.id === 'timeline_body') {
Timeline.menu.open(event, event);
}
},
menu: new Menu([
- {name: 'menu.timeline.add', icon: 'add', click: function(context) {
- var time = (context.offsetX+$('#timeline_inner').scrollLeft()-8) / Timeline.vue._data.size
- var row = Math.floor((context.offsetY-32) / 31 + 0.15)
- if (!Animator.selected) {
- Blockbench.showQuickMessage('message.no_animation_selected')
- return;
- }
- var bone = Animator.selected.getBoneAnimator()
- if (bone) {
- Undo.initEdit({keyframes: bone.keyframes, keep_saved: true})
- var kf = bone.addKeyframe(false, Timeline.snapTime(time), Animator.channel_index[row])
- kf.select().callMarker()
- Undo.finishEdit('add keyframe')
- } else {
- Blockbench.showQuickMessage('message.no_bone_selected')
- }
- }},
- 'paste'
+ 'paste',
+ 'select_all_keyframes'
])
}
Molang.global_variables = {
'true': 1,
'false': 0,
get 'query.anim_time'() {
- return Timeline.second;
+ return Timeline.time;
},
get 'query.life_time'() {
- return Timeline.second;
+ return Timeline.time;
},
get 'time'() {
- return Timeline.second;
+ return Timeline.time;
}
}
Molang.variableHandler = function (variable) {
@@ -1322,23 +1644,42 @@ onVueSetup(function() {
el: '#animations_list',
data: {
animations: Animator.animations
+ },
+ methods: {
+ sort(event) {
+ var item = Animator.animations.splice(event.oldIndex, 1)[0];
+ Animator.animations.splice(event.newIndex, 0, item);
+ },
+ choose(event) {
+ var item = Animator.animations[event.oldIndex];
+ }
}
})
Timeline.vue = new Vue({
- el: '#timeline_inner',
+ el: '#timeline_vue',
data: {
size: 150,
length: 10,
+ scroll_left: 0,
+ head_width: 170,
timecodes: [],
- keyframes: [],
- marker: Timeline.second
+ animators: Timeline.animators,
+ focus_channel: null,
+ marker: Timeline.time
+ },
+ methods: {
+ toggleAnimator(animator) {
+ animator.expanded = !animator.expanded;
+ },
+ removeAnimator(animator) {
+ Timeline.animators.remove(animator);
+ }
}
})
})
BARS.defineActions(function() {
- new Action({
- id: 'add_animation',
+ new Action('add_animation', {
icon: 'fa-plus-circle',
category: 'animation',
condition: () => Animator.open,
@@ -1349,8 +1690,7 @@ BARS.defineActions(function() {
}
})
- new Action({
- id: 'load_animation_file',
+ new Action('load_animation_file', {
icon: 'fa-file-video',
category: 'animation',
condition: () => Animator.open,
@@ -1372,11 +1712,9 @@ BARS.defineActions(function() {
})
}
})
- new Action({
- id: 'export_animation_file',
+ new Action('export_animation_file', {
icon: 'save',
category: 'animation',
- condition: () => Animator.open,
click: function () {
var content = autoStringify(Animator.buildFile())
var path = ModelMeta.animation_path
@@ -1399,8 +1737,7 @@ BARS.defineActions(function() {
}
})
- new Action({
- id: 'play_animation',
+ new Action('play_animation', {
icon: 'play_arrow',
category: 'animation',
keybind: new Keybind({key: 32}),
@@ -1418,8 +1755,7 @@ BARS.defineActions(function() {
}
}
})
- new NumSlider({
- id: 'slider_animation_length',
+ new NumSlider('slider_animation_length', {
category: 'animation',
condition: () => Animator.open && Animator.selected,
get: function() {
@@ -1432,8 +1768,7 @@ BARS.defineActions(function() {
Animator.selected.length = limitNumber(value, 0, 1e4)
}
})
- new NumSlider({
- id: 'slider_animation_speed',
+ new NumSlider('slider_animation_speed', {
category: 'animation',
condition: () => Animator.open,
get: function() {
@@ -1454,7 +1789,7 @@ BARS.defineActions(function() {
return 50;
}
}
- if (e.ctrlKey) {
+ if (e.ctrlOrCmd) {
if (val < 500) {
return 1;
} else {
@@ -1476,8 +1811,7 @@ BARS.defineActions(function() {
}
}
})
- new NumSlider({
- id: 'slider_keyframe_time',
+ new NumSlider('slider_keyframe_time', {
category: 'animation',
condition: () => Animator.open && Timeline.selected.length,
get: function() {
@@ -1499,13 +1833,20 @@ BARS.defineActions(function() {
Undo.finishEdit('move keyframes')
}
})
- new Action({
- id: 'reset_keyframe',
+ /*
+ new BarSlider('volume', {
+ category: 'animation',
+ min: 0, max: 100, step: 5, width: 80,
+ onChange: function(slider) {
+ Animator.volume = slider.get();
+ }
+ })*/
+ new Action('reset_keyframe', {
icon: 'replay',
category: 'animation',
condition: () => Animator.open && Timeline.selected.length,
click: function () {
- Undo.initEdit({keyframes: Timeline.selected, keep_saved: true})
+ Undo.initEdit({keyframes: Timeline.selected})
Timeline.selected.forEach((kf) => {
var n = kf.channel === 'scale' ? 1 : 0;
kf.extend({
@@ -1520,21 +1861,60 @@ BARS.defineActions(function() {
Animator.preview()
}
})
+ new Action('change_keyframe_file', {
+ icon: 'fa-file-audio',
+ category: 'animation',
+ condition: () => (Animator.open && Timeline.selected.length && Timeline.selected[0].channel == 'sound' && isApp),
+ click: function () {
+ Blockbench.import({
+ extensions: ['ogg'],
+ type: 'Audio File',
+ startpath: Timeline.selected[0].file
+ }, function(files) {
+
+ Undo.initEdit({keyframes: Timeline.selected})
+ Timeline.selected.forEach((kf) => {
+ if (kf.channel == 'sound') {
+ kf.file = files[0].path;
+ }
+ })
+ Undo.finishEdit('changed keyframe audio file')
+ })
+ }
+ })
+ new Action('reverse_keyframes', {
+ icon: 'swap_horizontal_circle',
+ category: 'animation',
+ condition: () => Animator.open && Timeline.selected.length,
+ click: function () {
+ var start = 1e9;
+ var end = 0;
+ Timeline.selected.forEach((kf) => {
+ start = Math.min(start, kf.time);
+ end = Math.max(end, kf.time);
+ })
+ Undo.initEdit({keyframes: Timeline.selected})
+ Timeline.selected.forEach((kf) => {
+ kf.time = end + start - kf.time;
+ })
+ Undo.finishEdit('reverse keyframes')
+ updateKeyframeSelection()
+ Animator.preview()
+ }
+ })
- new Action({
- id: 'previous_keyframe',
+ new Action('previous_keyframe', {
icon: 'fa-arrow-circle-left',
category: 'animation',
condition: () => Animator.open,
click: function () {
- var time = Timeline.second;
+ var time = Timeline.time;
function getDelta(kf, abs) {
return kf.time - time
}
var matches = []
- for (var i = 0; i < Timeline.keyframes.length; i++) {
- var kf = Timeline.keyframes[i]
+ for (var kf of Timeline.keyframes) {
let delta = getDelta(kf)
if (delta < 0) {
matches.push(kf)
@@ -1556,20 +1936,18 @@ BARS.defineActions(function() {
}
}
})
- new Action({
- id: 'next_keyframe',
+ new Action('next_keyframe', {
icon: 'fa-arrow-circle-right',
category: 'animation',
condition: () => Animator.open,
click: function () {
- var time = Timeline.second;
+ var time = Timeline.time;
function getDelta(kf, abs) {
return kf.time - time
}
var matches = []
- for (var i = 0; i < Timeline.keyframes.length; i++) {
- var kf = Timeline.keyframes[i]
+ for (var kf of Timeline.keyframes) {
let delta = getDelta(kf)
if (delta > 0) {
matches.push(kf)
@@ -1586,12 +1964,44 @@ BARS.defineActions(function() {
})
- new Action({
- id: 'select_all_keyframes',
+ new Action('select_all_keyframes', {
icon: 'select_all',
category: 'animation',
condition: () => Animator.open,
keybind: new Keybind({key: 65, ctrl: true}),
click: function () {selectAllKeyframes()}
})
+ new Action('clear_timeline', {
+ icon: 'clear_all',
+ category: 'animation',
+ condition: () => Animator.open,
+ click: function () {
+ Timeline.vue._data.animators.purge();
+ if (Group.selected) Animator.selected.getBoneAnimator().select();
+ }
+ })
+ new Action('select_effect_animator', {
+ icon: 'fa-magic',
+ category: 'animation',
+ condition: () => Animator.open,
+ click: function () {
+ if (!Animator.selected) return;
+ if (!Animator.selected.animators.effects) {
+ var ea = Animator.selected.animators.effects = new EffectAnimator(Animator.selected);
+ }
+ Animator.selected.animators.effects.select()
+ }
+ })
+ new BarSelect('timeline_focus', {
+ options: {
+ all: true,
+ rotation: tl('timeline.rotation'),
+ position: tl('timeline.position'),
+ scale: tl('timeline.scale'),
+ },
+ onChange: function(slider) {
+ var val = slider.get();
+ Timeline.vue._data.focus_channel = val != 'all' ? val : null;
+ }
+ })
})
diff --git a/js/api.js b/js/api.js
index 163dcc7ef..dd39ead9b 100644
--- a/js/api.js
+++ b/js/api.js
@@ -157,6 +157,9 @@ const Blockbench = {
buttons[options.confirm].addClass('confirm_btn')
buttons[options.cancel].addClass('cancel_btn')
jq_dialog.append($('
').append(buttons))
+ buttons.forEach(b => {
+ b.after(' ')
+ })
jq_dialog.addClass('draggable')
@@ -195,7 +198,7 @@ const Blockbench = {
})
},
addMenuEntry(name, icon, click) {
- var action = new Action({icon: icon, name: name, id: name, click: click})
+ var action = new Action(name, {icon: icon, name: name, click: click})
MenuBar.addAction(action, 'filter')
},
removeMenuEntry(name) {
@@ -428,6 +431,7 @@ const Blockbench = {
? options.startpath.replace(/\.\w+$/, '')
: options.name
}, function (file_path) {
+ if (!file_path) return;
var extension = pathToExtension(file_path);
if (!extension && options.extensions && options.extensions[0]) {
file_path += '.'+options.extensions[0]
@@ -443,7 +447,7 @@ const Blockbench = {
project_file
custom_writer
*/
- if (!isApp || file_path === undefined) {
+ if (!isApp || !file_path) {
return;
}
if (options.savetype === 'image' && typeof options.content === 'string') {
diff --git a/js/blockbench.js b/js/blockbench.js
index 8de420635..7ad2e0da2 100644
--- a/js/blockbench.js
+++ b/js/blockbench.js
@@ -81,7 +81,8 @@ function initializeApp() {
} else {
$('.web_only').remove()
}
- if (localStorage.getItem('welcomed_version') != appVersion) {
+ var last_welcome = localStorage.getItem('welcomed_version');
+ if (last_welcome.replace(/.\d+$/, '') != appVersion.replace(/.\d+$/, '')) {
Blockbench.addFlag('after_update')
localStorage.setItem('welcomed_version', appVersion)
}
@@ -276,9 +277,6 @@ function updateSelection() {
}
}
if (Modes.animate) {
- if (Animator.selected && Group.selected) {
- Animator.selected.getBoneAnimator().select()
- }
updateKeyframeSelection()
}
@@ -321,6 +319,7 @@ function createSelection() {
Group.selected.unselect()
}
var name_seg = $('#selgen_name').val().toUpperCase()
+ var tex_seg = $('#selgen_texture').val().toLowerCase()
var rdm = $('#selgen_random').val()/100
var array = elements
@@ -328,10 +327,20 @@ function createSelection() {
array = Group.selected.children
}
- array.forEach(function(s) {
- if (s.name.toUpperCase().includes(name_seg) === false) return;
+ array.forEach(function(obj) {
+ if (obj.name.toUpperCase().includes(name_seg) === false) return;
+ if (obj instanceof Cube && tex_seg && !Format.single_texture) {
+ var has_tex = false;
+ for (var key in obj.faces) {
+ var tex = obj.faces[key].getTexture();
+ if (tex && tex.name.includes(tex_seg)) {
+ has_tex = true
+ }
+ }
+ if (!has_tex) return;
+ }
if (Math.random() > rdm) return;
- selected.push(s)
+ selected.push(obj)
})
updateSelection()
if (selected.length) {
@@ -359,6 +368,7 @@ class Mode extends KeybindItem {
var scope = this;
if (Modes.selected) {
delete Modes[Modes.selected.id];
+ Modes.previous_id = Modes.selected.id;
}
if (typeof Modes.selected.onUnselect === 'function') {
Modes.selected.onUnselect()
@@ -438,6 +448,9 @@ BARS.defineActions(function() {
condition: () => Format,
keybind: new Keybind({key: 50}),
onSelect: () => {
+ if (Modes.previous_id == 'animate') {
+ Animator.preview();
+ }
Cube.all.forEach(cube => {
Canvas.buildGridBox(cube)
})
@@ -530,6 +543,9 @@ const Clipbench = {
} else if (Animator.open) {
if (Timeline.selected.length) {
Clipbench.setKeyframes(Timeline.selected)
+ if (cut) {
+ BarItems.delete.trigger()
+ }
}
} else if (p == 'uv' || p == 'preview') {
main_uv.copy(event)
@@ -537,6 +553,9 @@ const Clipbench = {
} else if (p == 'textures' && isApp) {
if (textures.selected) {
Clipbench.setTexture(textures.selected)
+ if (cut) {
+ BarItems.delete.trigger()
+ }
}
} else if (p == 'outliner') {
Clipbench.setElements()
@@ -571,19 +590,17 @@ const Clipbench = {
if (Clipbench.keyframes && Clipbench.keyframes.length) {
if (!Animator.selected) return;
- var bone = Animator.selected.getBoneAnimator()
- if (bone) {
+ var animator = Timeline.selected_animator
+ if (animator) {
var keyframes = [];
- Undo.initEdit({keyframes, keep_saved: true});
+ Undo.initEdit({keyframes});
Clipbench.keyframes.forEach(function(data, i) {
- var base_kf = new Keyframe(data);
- base_kf.time = Timeline.second + data.time_offset;
- bone.pushKeyframe(base_kf);
- keyframes.push(base_kf);
- base_kf.select(i ? {ctrlKey: true} : null)
+
+ var kf = animator.createKeyframe(data, Timeline.time + data.time_offset, data.channel)
+ keyframes.push(kf);
+ kf.select(i ? {ctrlOrCmd: true} : null)
})
Animator.preview()
- Vue.nextTick(Timeline.update);
Undo.finishEdit('paste keyframes');
}
}
@@ -697,15 +714,10 @@ const Clipbench = {
}
})
keyframes.forEach(function(kf) {
- Clipbench.keyframes.push({
- channel: kf.channel,
- x: kf.x,
- y: kf.y,
- z: kf.z,
- w: kf.w,
- isQuaternion: kf.isQuaternion,
- time_offset: kf.time - first.time,
- })
+ var copy = kf.getUndoCopy();
+ copy.time_offset = kf.time - first.time;
+ delete copy.animator;
+ Clipbench.keyframes.push(copy)
})
if (isApp) {
clipboard.writeHTML(JSON.stringify({type: 'keyframes', content: Clipbench.keyframes}))
diff --git a/js/desktop.js b/js/desktop.js
index af9810cd5..e3382ce7d 100644
--- a/js/desktop.js
+++ b/js/desktop.js
@@ -93,7 +93,7 @@ function addRecentProject(data) {
icon: data.icon,
day: new Date().dayOfYear()
})
- if (recent_projects.length > 12) {
+ if (recent_projects.length > Math.clamp(settings.recent_projects.value, 0, 256)) {
recent_projects.pop()
}
updateRecentProjects()
@@ -130,7 +130,7 @@ function checkForUpdates(instant) {
data =
`
${tl('dialog.update.latest')}: ${latest_version}
${tl('dialog.update.installed')}: ${appVersion}
-
`;
+
`;
if (instant) {
setTimeout(function() {
@@ -185,12 +185,9 @@ function installUpdate() {
}
function updateInstallationEnd() {
hideDialog();
- Blockbench.addFlag('update_restart');
- var exe_path = __dirname.split(osfs);
- exe_path.splice(-2);
- exe_path = exe_path.join(osfs)+osfs+'blockbench.exe';
- if (showSaveDialog(true)) {
- exec(exe_path);
+ if (showSaveDialog()) {
+ app.relaunch()
+ app.exit()
} else {
Blockbench.showQuickMessage('message.restart_to_update');
}
@@ -376,10 +373,5 @@ function closeBlockbenchWindow() {
localStorage.removeItem('backup_model')
EditSession.quit()
- if (!Blockbench.hasFlag('update_restart')) {
- return currentwindow.close();
- }
- setTimeout(function() {
- currentwindow.close();
- }, 12)
+ return currentwindow.close();
}
diff --git a/js/preview/display_mode.js b/js/display_mode.js
similarity index 99%
rename from js/preview/display_mode.js
rename to js/display_mode.js
index f90cb77c8..811ade7aa 100644
--- a/js/preview/display_mode.js
+++ b/js/display_mode.js
@@ -355,13 +355,13 @@ class refModel {
return this;
}
setModelVariant(variant) {
- this.variant = variant
+ this.variant = variant;
this.model.children.forEach((m) => {
if (m.r_model) {
m.visible = m.r_model === variant;
}
})
- if (displayReferenceObjects.active === this) {
+ if (display_mode && displayReferenceObjects.active === this) {
this.onload()
}
}
@@ -1721,12 +1721,13 @@ window.changeDisplaySkin = function() {
if (isApp) {
buttons.splice(1, 0, tl('message.display_skin.name'))
}
+ buttons.push('dialog.cancel');
Blockbench.showMessageBox({
translateKey: 'display_skin',
icon: 'icon-player',
buttons: buttons,
confirm: 0,
- cancel: isApp ? 2 : 1
+ cancel: buttons.length-1,
}, function(result) {
if (result === 0) {
Blockbench.import({
@@ -1882,8 +1883,7 @@ onVueSetup(function() {
})
BARS.defineActions(function() {
- new Action({
- id: 'add_display_preset',
+ new Action('add_display_preset', {
icon: 'add',
category: 'display',
condition: () => display_mode,
diff --git a/js/edit_sessions.js b/js/edit_sessions.js
index be51a70dc..a0bf8a7cc 100644
--- a/js/edit_sessions.js
+++ b/js/edit_sessions.js
@@ -397,14 +397,12 @@ onVueSetup(function() {
})
BARS.defineActions(function() {
- new Action({
- id: 'edit_session',
+ new Action('edit_session', {
icon: 'people',
category: 'blockbench',
click: EditSession.dialog
})
- new Action({
- id: 'toggle_chat',
+ new Action('toggle_chat', {
icon: 'keyboard_arrow_down',
condition: () => EditSession.active,
category: 'blockbench',
diff --git a/js/interface/actions.js b/js/interface/actions.js
index 237ce51db..3cf56f583 100644
--- a/js/interface/actions.js
+++ b/js/interface/actions.js
@@ -6,8 +6,8 @@ class MenuSeparator {
}
}
class BarItem {
- constructor(data) {
- this.id = data.id;
+ constructor(id, data) {
+ this.id = id;
if (!data.private) {
BarItems[this.id] = this;
}
@@ -80,8 +80,6 @@ class BarItem {
}
getNode() {
var scope = this;
- if (this.id === 'uv_rotation') {
- }
if (scope.nodes.length === 0) {
scope.nodes = [scope.node]
}
@@ -125,8 +123,12 @@ class BarItem {
}
}
class KeybindItem {
- constructor(data) {
- this.id = data.id
+ constructor(id, data) {
+ if (typeof id == 'object') {
+ data = id;
+ id = data.id;
+ }
+ this.id = id
this.type = 'keybind_item'
this.name = tl('keybind.'+this.id)
this.category = data.category ? data.category : 'misc'
@@ -141,8 +143,12 @@ class KeybindItem {
}
}
class Action extends BarItem {
- constructor(data) {
- super(data)
+ constructor(id, data) {
+ if (typeof id == 'object') {
+ data = id;
+ id = data.id;
+ }
+ super(id, data)
var scope = this;
this.type = 'action'
//Icon
@@ -226,8 +232,12 @@ class Action extends BarItem {
}
}
class Tool extends Action {
- constructor(data) {
- super(data)
+ constructor(id, data) {
+ if (typeof id == 'object') {
+ data = id;
+ id = data.id;
+ }
+ super(id, data);
var scope = this;
this.type = 'tool'
this.toolbar = data.toolbar;
@@ -237,6 +247,7 @@ class Tool extends Action {
this.selectCubes = data.selectCubes !== false;
this.paintTool = data.paintTool;
this.transformerMode = data.transformerMode;
+ this.animation_channel = data.animation_channel;
this.allowWireframe = data.allowWireframe !== false;
if (!this.condition) {
@@ -299,15 +310,23 @@ class Tool extends Action {
}
}
class Widget extends BarItem {
- constructor(data) {
- super(data);
+ constructor(id, data) {
+ if (typeof id == 'object') {
+ data = id;
+ id = data.id;
+ }
+ super(id, data);
this.type = 'widget';
//this.uniqueNode = true;
}
}
class NumSlider extends Widget {
- constructor(data) {
- super(data);
+ constructor(id, data) {
+ if (typeof id == 'object') {
+ data = id;
+ id = data.id;
+ }
+ super(id, data);
this.uv = !!data.uv;
this.type = 'numslider'
this.icon = 'code'
@@ -328,7 +347,7 @@ class NumSlider extends Widget {
} else {
this.interval = function(event) {
event = event||0;
- return canvasGridSize(event.shiftKey, event.ctrlKey);
+ return canvasGridSize(event.shiftKey, event.ctrlOrCmd);
};
}
if (typeof data.getInterval === 'function') {
@@ -395,6 +414,12 @@ class NumSlider extends Widget {
scope.jq_inner.addClass('editing')
scope.jq_inner.focus()
document.execCommand('selectAll')
+ })
+ .dblclick(function(event) {
+ if (event.target != this) return;
+ scope.jq_inner.text('0');
+ scope.input()
+
});
//Arrows
this.jq_outer
@@ -445,7 +470,7 @@ class NumSlider extends Widget {
this.change(difference)
this.update()
}
- input(obj) {
+ input() {
if (typeof this.onBefore === 'function') {
this.onBefore()
}
@@ -531,14 +556,18 @@ class NumSlider extends Widget {
}
}
class BarSlider extends Widget {
- constructor(data) {
- super(data)
+ constructor(id, data) {
+ if (typeof id == 'object') {
+ data = id;
+ id = data.id;
+ }
+ super(id, data);
var scope = this;
this.type = 'slider'
this.icon = 'fa-sliders-h'
this.value = data.value||0
this.node = $('
'+
- ''+data.text||''+'
').get(0)
@@ -683,8 +720,12 @@ class BarText extends Widget {
}
}
class ColorPicker extends Widget {
- constructor(data) {
- super(data)
+ constructor(id, data) {
+ if (typeof id == 'object') {
+ data = id;
+ id = data.id;
+ }
+ super(id, data);
var scope = this;
this.type = 'color_picker'
this.icon = 'color_lens'
@@ -925,69 +966,63 @@ const BARS = {
BarItems = {}
//Extras
- new KeybindItem({
- id: 'preview_select',
+ new KeybindItem('preview_select', {
category: 'navigate',
keybind: new Keybind({key: Blockbench.isMobile ? 0 : 1, ctrl: null, shift: null, alt: null})
})
- new KeybindItem({
- id: 'preview_rotate',
+ new KeybindItem('preview_rotate', {
category: 'navigate',
keybind: new Keybind({key: 1})
})
- new KeybindItem({
- id: 'preview_drag',
+ new KeybindItem('preview_drag', {
category: 'navigate',
keybind: new Keybind({key: 3})
})
- new KeybindItem({
- id: 'confirm',
+ new KeybindItem('confirm', {
category: 'navigate',
keybind: new Keybind({key: 13})
})
- new KeybindItem({
- id: 'cancel',
+ new KeybindItem('cancel', {
category: 'navigate',
keybind: new Keybind({key: 27})
})
//Tools
- new Tool({
- id: 'move_tool',
+ new Tool('move_tool', {
icon: 'fas fa-hand-paper',
category: 'tools',
selectFace: true,
transformerMode: 'translate',
+ animation_channel: 'position',
toolbar: Blockbench.isMobile ? 'element_position' : 'main_tools',
alt_tool: 'resize_tool',
modes: ['edit', 'display', 'animate'],
keybind: new Keybind({key: 86}),
})
- new Tool({
- id: 'resize_tool',
+ new Tool('resize_tool', {
icon: 'open_with',
category: 'tools',
selectFace: true,
transformerMode: 'scale',
+ animation_channel: 'scale',
toolbar: Blockbench.isMobile ? 'element_size' : 'main_tools',
alt_tool: 'move_tool',
modes: ['edit', 'display', 'animate'],
keybind: new Keybind({key: 83}),
})
- new Tool({
- id: 'rotate_tool',
+ new Tool('rotate_tool', {
icon: 'sync',
category: 'tools',
selectFace: true,
transformerMode: 'rotate',
+ animation_channel: 'rotation',
toolbar: Blockbench.isMobile ? 'element_rotation' : 'main_tools',
alt_tool: 'pivot_tool',
modes: ['edit', 'display', 'animate'],
keybind: new Keybind({key: 82}),
})
- new Tool({
- id: 'pivot_tool',
+ new Tool('pivot_tool', {
icon: 'gps_fixed',
category: 'tools',
transformerMode: 'translate',
@@ -996,8 +1031,7 @@ const BARS = {
modes: ['edit', 'animate'],
keybind: new Keybind({key: 80}),
})
- new Tool({
- id: 'vertex_snap_tool',
+ new Tool('vertex_snap_tool', {
icon: 'icon-vertexsnap',
transformerMode: 'hidden',
toolbar: 'vertex_snap',
@@ -1019,16 +1053,14 @@ const BARS = {
Blockbench.removeListener('update_selection', Vertexsnap.select)
}
})
- new BarSelect({
- id: 'vertex_snap_mode',
+ new BarSelect('vertex_snap_mode', {
options: {
move: true,
scale: true
},
category: 'edit'
})
- new Action({
- id: 'swap_tools',
+ new Action('swap_tools', {
icon: 'swap_horiz',
category: 'tools',
condition: () => !Modes.animate,
@@ -1041,8 +1073,7 @@ const BARS = {
})
//File
- new Action({
- id: 'open_model_folder',
+ new Action('open_model_folder', {
icon: 'folder_open',
category: 'file',
condition: () => {return isApp && (ModelMeta.save_path || ModelMeta.export_path)},
@@ -1050,8 +1081,7 @@ const BARS = {
shell.showItemInFolder(ModelMeta.export_path || ModelMeta.save_path);
}
})
- new Action({
- id: 'open_backup_folder',
+ new Action('open_backup_folder', {
icon: 'fa-archive',
category: 'file',
condition: () => isApp,
@@ -1059,22 +1089,19 @@ const BARS = {
shell.showItemInFolder(app.getPath('userData')+osfs+'backups'+osfs+'.')
}
})
- new Action({
- id: 'settings_window',
+ new Action('settings_window', {
icon: 'settings',
category: 'blockbench',
keybind: new Keybind({key: 69, ctrl: true}),
click: function () {Settings.open()}
})
- new Action({
- id: 'update_window',
+ new Action('update_window', {
icon: 'update',
category: 'blockbench',
condition: isApp,
click: function () {checkForUpdates()}
})
- new Action({
- id: 'reload',
+ new Action('reload', {
icon: 'refresh',
category: 'file',
condition: () => Blockbench.hasFlag('dev'),
@@ -1082,32 +1109,28 @@ const BARS = {
})
//Edit Generic
- new Action({
- id: 'copy',
+ new Action('copy', {
icon: 'fa-copy',
category: 'edit',
work_in_dialog: true,
keybind: new Keybind({key: 67, ctrl: true, shift: null}),
click: function (event) {Clipbench.copy(event)}
})
- new Action({
- id: 'paste',
+ new Action('paste', {
icon: 'fa-clipboard',
category: 'edit',
work_in_dialog: true,
keybind: new Keybind({key: 86, ctrl: true, shift: null}),
click: function (event) {Clipbench.paste(event)}
})
- new Action({
- id: 'cut',
+ new Action('cut', {
icon: 'fa-cut',
category: 'edit',
work_in_dialog: true,
keybind: new Keybind({key: 88, ctrl: true, shift: null}),
click: function (event) {Clipbench.copy(event, true)}
})
- new Action({
- id: 'rename',
+ new Action('rename', {
icon: 'text_format',
category: 'edit',
keybind: new Keybind({key: 113}),
@@ -1120,8 +1143,7 @@ const BARS = {
}
})
- new Action({
- id: 'delete',
+ new Action('delete', {
icon: 'delete',
category: 'edit',
//condition: () => (Modes.edit && (selected.length || Group.selected)),
@@ -1160,8 +1182,7 @@ const BARS = {
})
- new Action({
- id: 'duplicate',
+ new Action('duplicate', {
icon: 'content_copy',
category: 'edit',
condition: () => (Animator.selected && Modes.animate) || (Modes.edit && (selected.length || Group.selected)),
@@ -1196,48 +1217,42 @@ const BARS = {
//Move Cube Keys
- new Action({
- id: 'move_up',
+ new Action('move_up', {
icon: 'arrow_upward',
category: 'transform',
condition: () => (selected.length && !open_menu && Modes.id === 'edit'),
keybind: new Keybind({key: 38, ctrl: null, shift: null}),
click: function (e) {moveCubesRelative(-1, 2, e)}
})
- new Action({
- id: 'move_down',
+ new Action('move_down', {
icon: 'arrow_downward',
category: 'transform',
condition: () => (selected.length && !open_menu && Modes.id === 'edit'),
keybind: new Keybind({key: 40, ctrl: null, shift: null}),
click: function (e) {moveCubesRelative(1, 2, e)}
})
- new Action({
- id: 'move_left',
+ new Action('move_left', {
icon: 'arrow_back',
category: 'transform',
condition: () => (selected.length && !open_menu && Modes.id === 'edit'),
keybind: new Keybind({key: 37, ctrl: null, shift: null}),
click: function (e) {moveCubesRelative(-1, 0, e)}
})
- new Action({
- id: 'move_right',
+ new Action('move_right', {
icon: 'arrow_forward',
category: 'transform',
condition: () => (selected.length && !open_menu && Modes.id === 'edit'),
keybind: new Keybind({key: 39, ctrl: null, shift: null}),
click: function (e) {moveCubesRelative(1, 0, e)}
})
- new Action({
- id: 'move_forth',
+ new Action('move_forth', {
icon: 'keyboard_arrow_up',
category: 'transform',
condition: () => (selected.length && !open_menu && Modes.id === 'edit'),
keybind: new Keybind({key: 33, ctrl: null, shift: null}),
click: function (e) {moveCubesRelative(-1, 1, e)}
})
- new Action({
- id: 'move_back',
+ new Action('move_back', {
icon: 'keyboard_arrow_down',
category: 'transform',
condition: () => (selected.length && !open_menu && Modes.id === 'edit'),
@@ -1246,14 +1261,12 @@ const BARS = {
})
//Settings
- new Action({
- id: 'reset_keybindings',
+ new Action('reset_keybindings', {
icon: 'replay',
category: 'blockbench',
click: function () {Keybinds.reset()}
})
- new Action({
- id: 'import_layout',
+ new Action('import_layout', {
icon: 'folder',
category: 'blockbench',
click: function () {
@@ -1265,8 +1278,7 @@ const BARS = {
})
}
})
- new Action({
- id: 'export_layout',
+ new Action('export_layout', {
icon: 'style',
category: 'blockbench',
click: function () {
@@ -1277,8 +1289,7 @@ const BARS = {
})
}
})
- new Action({
- id: 'reset_layout',
+ new Action('reset_layout', {
icon: 'replay',
category: 'blockbench',
click: function () {
@@ -1295,8 +1306,7 @@ const BARS = {
})
//View
- new Action({
- id: 'fullscreen',
+ new Action('fullscreen', {
icon: 'fullscreen',
category: 'view',
condition: isApp,
@@ -1305,28 +1315,24 @@ const BARS = {
currentwindow.setFullScreen(!currentwindow.isFullScreen())
}
})
- new Action({
- id: 'zoom_in',
+ new Action('zoom_in', {
icon: 'zoom_in',
category: 'view',
click: function () {setZoomLevel('in')}
})
- new Action({
- id: 'zoom_out',
+ new Action('zoom_out', {
icon: 'zoom_out',
category: 'view',
click: function () {setZoomLevel('out')}
})
- new Action({
- id: 'zoom_reset',
+ new Action('zoom_reset', {
icon: 'zoom_out_map',
category: 'view',
click: function () {setZoomLevel('reset')}
})
//Find Action
- new Action({
- id: 'action_control',
+ new Action('action_control', {
icon: 'fullscreen',
category: 'blockbench',
keybind: new Keybind({key: 70}),
@@ -1505,6 +1511,7 @@ const BARS = {
id: 'keyframe',
children: [
'slider_keyframe_time',
+ 'change_keyframe_file',
'reset_keyframe'
],
default_place: true
@@ -1512,6 +1519,10 @@ const BARS = {
Toolbars.timeline = new Toolbar({
id: 'timeline',
children: [
+ 'timeline_focus',
+ 'clear_timeline',
+ 'select_effect_animator',
+ '_',
'slider_animation_speed',
'previous_keyframe',
'next_keyframe',
@@ -1725,7 +1736,7 @@ const ActionControl = {
get open() {return ActionControl.vue._data.open},
set open(state) {ActionControl.vue._data.open = !!state},
type: 'action_selector',
- max_length: 16,
+ max_length: 32,
select() {
ActionControl.open = true;
open_interface = ActionControl;
diff --git a/js/interface/dialog.js b/js/interface/dialog.js
index 7eab35ed7..6956dd5b6 100644
--- a/js/interface/dialog.js
+++ b/js/interface/dialog.js
@@ -137,7 +137,7 @@ function Dialog(settings) {
var buttons = []
scope.buttons.forEach(function(b, i) {
- var btn = $('
')
+ var btn = $('
')
buttons.push(btn)
})
buttons[scope.confirmIndex] && buttons[scope.confirmIndex].addClass('confirm_btn')
@@ -149,15 +149,15 @@ function Dialog(settings) {
} else if (this.singleButton) {
jq_dialog.append('
' +
- '' +
+ '' +
'
')
} else {
- jq_dialog.append(['
',
- '',
- '',
- '
'].join(''))
+ jq_dialog.append(`
+
+
+
`)
}
jq_dialog.append('
clear
')
diff --git a/js/interface/interface.js b/js/interface/interface.js
index 487ba739a..1c57d891f 100644
--- a/js/interface/interface.js
+++ b/js/interface/interface.js
@@ -171,6 +171,7 @@ var Interface = {
right_bar_width: 314,
quad_view_x: 50,
quad_view_y: 50,
+ timeline_height: 260,
left_bar: ['uv', 'textures', 'display', 'animations', 'keyframe', 'variable_placeholders'],
right_bar: ['color', 'outliner', 'chat']
},
@@ -222,11 +223,14 @@ var Interface = {
condition: function() {return quad_previews.enabled},
get: function() {return Interface.data.quad_view_x},
set: function(o, diff) {Interface.data.quad_view_x = limitNumber(o + diff/$('#preview').width()*100, 5, 95)},
- position: function(line) {line.setPosition({
- top: 32,
- bottom: 0,
- left: Interface.data.left_bar_width + $('#preview').width()*Interface.data.quad_view_x/100
- })}
+ position: function(line) {
+ var p = document.getElementById('preview')
+ line.setPosition({
+ top: 32,
+ bottom: p ? window.innerHeight - (p.clientHeight + p.offsetTop) : 0,
+ left: Interface.data.left_bar_width + $('#preview').width()*Interface.data.quad_view_x/100
+ }
+ )}
}),
quad_view_y: new ResizeLine({
id: 'quad_view_y',
@@ -241,6 +245,20 @@ var Interface = {
right: Interface.data.right_bar_width+2,
top: $('#preview').offset().top + $('#preview').height()*Interface.data.quad_view_y/100
})}
+ }),
+ timeline: new ResizeLine({
+ id: 'timeline',
+ horizontal: true,
+ condition: function() {return Modes.animate},
+ get: function() {return Interface.data.timeline_height},
+ set: function(o, diff) {
+ Interface.data.timeline_height = limitNumber(o - diff, 150, document.body.clientHeight-120)
+ },
+ position: function(line) {line.setPosition({
+ left: Interface.data.left_bar_width+2,
+ right: Interface.data.right_bar_width+2,
+ top: $('#timeline').offset().top
+ })}
})
},
status_bar: {},
@@ -295,6 +313,47 @@ function setupInterface() {
}
})
+ //Colors
+ var color_wrapper = $('#color_wrapper')
+ for (var key in app_colors) {
+ if (app_colors[key] && app_colors[key].hex) {
+ (() => {
+ var scope_key = key;
+ var hex = app_colors[scope_key].hex;
+ if (key == 'text_acc') key = 'accent_text';
+ var field = $(`
+
+
+
${tl('layout.color.'+key)}
+
${tl('layout.color.'+key+'.desc')}
+
+
`);
+ color_wrapper.append(field);
+
+ var last_color = hex;
+ field.spectrum({
+ preferredFormat: "hex",
+ color: hex,
+ showAlpha: false,
+ showInput: true,
+ move(c) {
+ app_colors[scope_key].hex = c.toHexString();
+ updateUIColor();
+ },
+ change(c) {
+ last_color = c.toHexString();
+ },
+ hide(c) {
+ app_colors[scope_key].hex = last_color;
+ field.spectrum('set', last_color)
+ updateUIColor();
+ }
+ });
+ })()
+ }
+ }
+
+
//Panels
Interface.Panels.uv = new Panel({
id: 'uv',
@@ -441,8 +500,7 @@ function setupInterface() {
//Tooltip Fix
- $('.tool').on('mouseenter', function() {
-
+ $(document).on('mouseenter', '.tool', function() {
var tooltip = $(this).find('div.tooltip')
if (!tooltip || typeof tooltip.offset() !== 'object') return;
//Left
@@ -456,8 +514,11 @@ function setupInterface() {
if (tooltip.css('right') === '-4px') {
tooltip.css('right', 'auto')
}
+
if ((tooltip.offset().left + tooltip.width()) - $(window).width() > 4) {
tooltip.css('right', '-4px')
+ } else if ($(this).parent().css('position') == 'relative') {
+ tooltip.css('right', '0')
}
})
@@ -563,6 +624,7 @@ function updateInterfacePanels() {
$('.quad_canvas_wrapper.qcw_y').css('height', Interface.data.quad_view_y+'%')
$('.quad_canvas_wrapper:not(.qcw_x)').css('width', (100-Interface.data.quad_view_x)+'%')
$('.quad_canvas_wrapper:not(.qcw_y)').css('height', (100-Interface.data.quad_view_y)+'%')
+ $('#timeline').css('height', Interface.data.timeline_height+'px')
for (var key in Interface.Resizers) {
var resizer = Interface.Resizers[key]
resizer.update()
@@ -728,16 +790,6 @@ function colorSettingsSetup(reset) {
updateUIColor()
buildGrid()
}
-function initUIColor(event) {
- var type = $(event.target).attr('id').split('color_')[1]
- $('input#color_'+type).val(app_colors[type].hex)
-}
-function changeUIColor(event) {
- var type = $(event.target).attr('id').split('color_')[1]
-
- app_colors[type].hex = $('input#color_'+type).val()
- updateUIColor()
-}
function changeUIFont(type) {
var font = $('#layout_font_'+type).val()
app_colors[type].font = font
@@ -766,8 +818,9 @@ function updateUIColor() {
$('meta[name=theme-color]').attr('content', app_colors.border.hex)
var c_outline = parseInt('0x'+app_colors.accent.hex.replace('#', ''))
- if (!gizmo_colors.outline || c_outline !== gizmo_colors.outline.getHex()) {
- gizmo_colors.outline = new THREE.Color( c_outline )
+
+ if (c_outline !== gizmo_colors.outline.getHex()) {
+ gizmo_colors.outline.set(c_outline)
Canvas.outlineMaterial.color = gizmo_colors.outline
}
var w_wire = parseInt('0x'+app_colors.wireframe.hex.replace('#', ''))
diff --git a/js/interface/menu.js b/js/interface/menu.js
index 66f6d41af..d93d1483c 100644
--- a/js/interface/menu.js
+++ b/js/interface/menu.js
@@ -198,7 +198,7 @@ class Menu {
position = scope.label
}
var offset_left = $(position).offset().left;
- var offset_top = $(position).offset().top + $(position).height()+1;
+ var offset_top = $(position).offset().top + $(position).height()+3;
}
if (offset_left > $(window).width() - el_width) {
@@ -405,6 +405,7 @@ const MenuBar = {
'export_entity',
'export_class_entity',
'export_optifine_full',
+ 'export_optifine_part',
'export_obj',
'upload_sketchfab',
]},
@@ -564,8 +565,11 @@ const MenuBar = {
'copy',
'paste',
'select_all_keyframes',
+ 'reverse_keyframes',
'delete',
'_',
+ 'select_effect_animator',
+ '_',
'load_animation_file',
'export_animation_file',
], () => Animator.open)
diff --git a/js/interface/settings.js b/js/interface/settings.js
index f6463f0bd..dd8b6e1c0 100644
--- a/js/interface/settings.js
+++ b/js/interface/settings.js
@@ -6,6 +6,7 @@ const Settings = {
//General
language: {value: 'en', type: 'select', options: Language.options},
username: {value: '', type: 'text'},
+ recent_projects: {value: 12, max: 128, min: 0, type: 'number', condition: isApp},
backup_interval: {value: 10, type: 'number', condition: isApp},
backup_retain: {value: 30, type: 'number', condition: isApp},
//Preview
@@ -18,6 +19,7 @@ const Settings = {
transparency: {category: 'preview', value: true},
outliner_colors: {category: 'preview', value: false},
texture_fps: {category: 'preview', value: 2, type: 'number'},
+ volume: {category: 'preview', value: 80, type: 'number'},
display_skin: {category: 'preview', value: false, type: 'click', condition: isApp, icon: 'icon-player', click: function() { changeDisplaySkin() }},
//Edit
undo_limit: {category: 'edit', value: 128, type: 'number'},
diff --git a/js/io/bbmodel.js b/js/io/bbmodel.js
index 7b840c56a..93843663c 100644
--- a/js/io/bbmodel.js
+++ b/js/io/bbmodel.js
@@ -212,8 +212,7 @@ var codec = new Codec('project', {
})
BARS.defineActions(function() {
- codec.export_action = new Action({
- id: 'save_project',
+ codec.export_action = new Action('save_project', {
icon: 'save',
category: 'file',
keybind: new Keybind({key: 83, ctrl: true, alt: true}),
@@ -227,8 +226,7 @@ BARS.defineActions(function() {
}
})
- new Action({
- id: 'save_project_as',
+ new Action('save_project_as', {
icon: 'save',
category: 'file',
keybind: new Keybind({key: 83, ctrl: true, alt: true, shift: true}),
diff --git a/js/io/bedrock.js b/js/io/bedrock.js
index b6d88d7f1..56b6c9afa 100644
--- a/js/io/bedrock.js
+++ b/js/io/bedrock.js
@@ -1,4 +1,5 @@
function findEntityTexture(mob, return_path) {
+ if (!mob) return;
var textures = {
'llamaspit': 'llama/spit',
'llama': 'llama/llama_creamy',
diff --git a/js/io/bedrock_old.js b/js/io/bedrock_old.js
index 54c79733c..e70efb3b5 100644
--- a/js/io/bedrock_old.js
+++ b/js/io/bedrock_old.js
@@ -477,7 +477,7 @@ var codec = new Codec('bedrock_old', {
var format = new ModelFormat({
id: 'bedrock_old',
extension: 'json',
- icon: 'icon-format_bedrock',
+ icon: 'icon-format_bedrock_legacy',
show_on_start_screen: false,
box_uv: true,
single_texture: true,
diff --git a/js/io/io.js b/js/io/io.js
index 4961f5d26..8faae0bc1 100644
--- a/js/io/io.js
+++ b/js/io/io.js
@@ -20,8 +20,8 @@ class ModelFormat {
constructor(data) {
Formats[data.id] = this;
this.id = data.id;
- this.name = tl('format.'+this.id);
- this.description = tl('format.'+this.id+'.desc');
+ this.name = data.name || tl('format.'+this.id);
+ this.description = data.description || tl('format.'+this.id+'.desc');
this.show_on_start_screen = true;
this.box_uv = false;
@@ -74,8 +74,8 @@ class ModelFormat {
}
var center = Format.bone_rig ? 8 : 0;
previews.forEach(preview => {
+ preview.camOrtho.position.y += center - preview.controls.target.y;
preview.controls.target.set(0, center, 0);
- preview.camOrtho.position.add(preview.controls.target);
})
updateSelection()
Modes.vue.$forceUpdate()
@@ -355,7 +355,7 @@ function newProject(format, force) {
function setupDragHandlers() {
Blockbench.addDragHandler(
'model',
- {extensions: ['json', 'jem', 'bbmodel']},
+ {extensions: ['json', 'jem', 'jpm', 'bbmodel']},
function(files) {
loadModelFile(files[0])
}
@@ -415,6 +415,8 @@ function loadModelFile(file) {
}
} else if (extension == 'jem') {
Codecs.optifine_entity.load(model, file)
+ } else if (extension == 'jpm') {
+ Codecs.optifine_part.load(model, file)
}
EditSession.initNewModel()
}
@@ -820,8 +822,7 @@ BARS.defineActions(function() {
optional_box_uv: true,
})
//Project
- new Action({
- id: 'project_window',
+ new Action('project_window', {
icon: 'featured_play_list',
category: 'file',
click: function () {
@@ -831,10 +832,11 @@ BARS.defineActions(function() {
title: 'dialog.project.title',
width: 540,
form: {
+ format: {type: 'text', label: 'data.format', text: Format.name||'unknown'},
name: {label: 'dialog.project.name', value: Project.name},
parent: {label: 'dialog.project.parent', value: Project.parent, condition: !Format.bone_rig},
geometry_name: {label: 'dialog.project.geoname', value: Project.geometry_name, condition: Format.bone_rig},
- ambientocclusion: {label: 'dialog.project.ao', type: 'checkbox', value: Project.ambientocclusion, condition: !Format.bone_rig},
+ ambientocclusion: {label: 'dialog.project.ao', type: 'checkbox', value: Project.ambientocclusion, condition: Format.id == 'java_block'},
box_uv: {label: 'dialog.project.box_uv', type: 'checkbox', value: Project.box_uv, condition: Format.optional_box_uv},
texture_width: {
label: 'dialog.project.width',
@@ -848,7 +850,6 @@ BARS.defineActions(function() {
value: Project.texture_height,
min: 1
},
-
},
onConfirm: function(formResult) {
var save;
@@ -883,8 +884,7 @@ BARS.defineActions(function() {
dialog.show()
}
})
- new Action({
- id: 'close_project',
+ new Action('close_project', {
icon: 'cancel_presentation',
category: 'file',
condition: () => (!EditSession.active || EditSession.hosting) && Format,
@@ -897,8 +897,7 @@ BARS.defineActions(function() {
}
}
})
- new Action({
- id: 'convert_project',
+ new Action('convert_project', {
icon: 'fas.fa-file-import',
category: 'file',
condition: () => (!EditSession.active || EditSession.hosting),
@@ -916,7 +915,7 @@ BARS.defineActions(function() {
form: {
text: {type: 'text', text: 'dialog.convert_project.text'},
format: {
- label: 'dialog.convert_project.format',
+ label: 'data.format',
type: 'select',
default: Format.id,
options,
@@ -935,23 +934,21 @@ BARS.defineActions(function() {
}
})
//Import
- new Action({
- id: 'open_model',
+ new Action('open_model', {
icon: 'assessment',
category: 'file',
keybind: new Keybind({key: 79, ctrl: true}),
condition: () => (!EditSession.active || EditSession.hosting),
click: function () {
Blockbench.import({
- extensions: ['json', 'jem', 'bbmodel'],
+ extensions: ['json', 'jem', 'jpm', 'bbmodel'],
type: 'Model'
}, function(files) {
loadModelFile(files[0])
})
}
})
- new Action({
- id: 'add_model',
+ new Action('add_model', {
icon: 'assessment',
category: 'file',
condition: _ => (Format.id == 'java_block' || Format.id == 'free'),
@@ -968,8 +965,7 @@ BARS.defineActions(function() {
})
}
})
- new Action({
- id: 'extrude_texture',
+ new Action('extrude_texture', {
icon: 'eject',
category: 'file',
condition: _ => !Project.box_uv,
@@ -987,8 +983,7 @@ BARS.defineActions(function() {
}
})
//Export
- new Action({
- id: 'export_over',
+ new Action('export_over', {
icon: 'save',
category: 'file',
keybind: new Keybind({key: 83, ctrl: true}),
@@ -1007,6 +1002,8 @@ BARS.defineActions(function() {
Blockbench.writeFile(ModelMeta.animation_path, {
content: autoStringify(Animator.buildFile())
})
+ } else if (Animator.animations.length) {
+ BarItems.export_animation_file.trigger()
}
}
} else {
@@ -1015,8 +1012,7 @@ BARS.defineActions(function() {
}
})
if (!isApp) {
- new Action({
- id: 'export_asset_archive',
+ new Action('export_asset_archive', {
icon: 'archive',
category: 'file',
condition: _ => Format && Format.codec,
@@ -1043,8 +1039,7 @@ BARS.defineActions(function() {
}
})
}
- new Action({
- id: 'upload_sketchfab',
+ new Action('upload_sketchfab', {
icon: 'icon-sketchfab',
category: 'file',
click: function(ev) {
diff --git a/js/io/java_block.js b/js/io/java_block.js
index a77757c5d..364df4e56 100644
--- a/js/io/java_block.js
+++ b/js/io/java_block.js
@@ -296,12 +296,12 @@ var codec = new Codec('java_block', {
base_cube.faces[face].texture = null
base_cube.faces[face].uv = [0,0,0,0]
} else {
- delete base_cube.faces[face].texture;
if (typeof obj.faces[face].uv === 'object') {
uv_stated = true
}
if (obj.faces[face].texture === '#missing') {
-
+ base_cube.faces[face].texture = false;
+
} else if (obj.faces[face].texture) {
var id = obj.faces[face].texture.replace(/^#/, '')
var t = texture_ids[id]
diff --git a/js/io/modded_entity.js b/js/io/modded_entity.js
index 764261d2b..ce501757a 100644
--- a/js/io/modded_entity.js
+++ b/js/io/modded_entity.js
@@ -145,6 +145,72 @@ var codec = new Codec('modded_entity', {
'\n}';
return model;
},
+ parse(model, path, add) {
+ // WIP //
+ var lines = [];
+ model.split('\n').forEach(l => {
+ l = l.replace(/\/\*[^(\*\/)]*\*\/|\/\/.*/g, '').trim().replace(/;$/, '');
+ if (l) {
+ lines.push(l)
+ }
+ })
+
+ var getArgs = function(input) {
+ var i = input.search('(')
+ var args = input.substr(i+1).replace(/\)$/, '');
+ return args.split(/, ?/);
+ }
+ var scope = 0,
+ bones = {},
+ geo_name;
+
+ lines.forEach(line => {
+ if (scope == 0) {
+ if (/^public class/.test(line)) {
+ scope = 1;
+ geo_name = line.split(' ')[2];
+ }
+ } else if (scope == 1) {
+ line = line.replace(/Public|Static|Final|Private|Void/g, '').trim();
+ if (line.substr(0, 13) == 'ModelRenderer') {
+ bones[line.split('')[1]] = new Group().init();
+ } else if (line.substr(0, geo_name.length) == geo_name) {
+ scope = 2;
+ }
+
+ } else if (scope == 2) {
+ line = line.replace(/^this\./, '');
+ var key = line.match(/^\w+(?=[\.| |=])/);
+ key = key ? key[0] : '';
+ if (key.length) {
+ var action = line.substr(key.length).trim();
+ if (key == 'textureWidth') {
+ Project.texture_width = parseInt(action.replace(/=/), '');
+ } else
+ if (key == 'textureHeight') {
+ Project.texture_height = parseInt(action.replace(/=/), '');
+ } else
+ if (bones[key]) {
+ if (action.match(/^= ?new ModelRenderer\(/)) {
+ var args = getArgs(action);
+ } else
+ if (action.match(/^\.setRotationPoint\(/)) {
+ var args = getArgs(action);
+ var origin = [];
+ args.forEach((n, i) => {
+ origin[i] = parseInt(n.replace(/F/, '')) * (i == 2 ? 1 : -1);
+ })
+ bones[key].extend({origin})
+ } else
+ if (action.match(/^\.cubeList\.add\(/)) {
+
+ }
+ }
+ }
+ }
+ })
+
+ }
})
var format = new ModelFormat({
diff --git a/js/io/optifine.js b/js/io/optifine_jem.js
similarity index 76%
rename from js/io/optifine.js
rename to js/io/optifine_jem.js
index cbda0fc47..6c507e6eb 100644
--- a/js/io/optifine.js
+++ b/js/io/optifine_jem.js
@@ -232,67 +232,6 @@ var codec = new Codec('optifine_entity', {
}
})
-var part_codec = new Codec('optifine_part', {
- name: 'OptiFine Part',
- extension: 'jpm',
- parse(model, path) {
- Project.box_uv = false;
- var new_cubes = [];
- var import_group = new Group({
- name: pathToName(path)
- }).init();
- Undo.initEdit({elements: new_cubes, outliner: true})
-
- var resolution = model.textureSize
- function convertUVCoords(uv) {
- if (uv instanceof Array && resolution instanceof Array) {
- uv.forEach((n, i) => {
- uv[i] *= 16 / resolution[i%2];
- })
- }
- return uv;
- }
- function addSubmodel(submodel) {
- if (submodel.boxes) {
- submodel.boxes.forEach(function(box) {
- var cs = box.coordinates
- if (cs && cs.length >= 6) {
- base_cube = new Cube({
- from: [
- cs[0],
- cs[1],
- cs[2]
- ],
- to: [
- cs[0] + cs[3],
- cs[1] + cs[4],
- cs[2] + cs[5]
- ],
- name: submodel.id,
- faces: {
- north: {uv: convertUVCoords(box.uvNorth)},
- east: {uv: convertUVCoords(box.uvEast)},
- south: {uv: convertUVCoords(box.uvSouth)},
- west: {uv: convertUVCoords(box.uvWest)},
- up: {uv: convertUVCoords(box.uvUp)},
- down: {uv: convertUVCoords(box.uvDown)},
- },
- rotation: submodel.rotate
- }).init().addTo(import_group)
- new_cubes.push(base_cube);
- }
- })
- }
- if (submodel.submodels) {
- submodel.submodels.forEach(addSubmodel)
- }
- }
- import_group.addTo()
- Undo.finishEdit('add jpm model')
- addSubmodel(model)
- Canvas.updateAll()
- }
-})
var format = new ModelFormat({
id: 'optifine_entity',
@@ -307,9 +246,9 @@ var format = new ModelFormat({
})
codec.format = format;
+
BARS.defineActions(function() {
- codec.export_action = new Action({
- id: 'export_optifine_full',
+ codec.export_action = new Action('export_optifine_full', {
icon: 'icon-optifine_file',
category: 'file',
condition: () => Format == format,
@@ -317,24 +256,6 @@ BARS.defineActions(function() {
codec.export()
}
})
- new Action({
- id: 'import_optifine_part',
- icon: 'icon-optifine_file',
- category: 'file',
- condition: () => Format == format,
- click: function () {
- Blockbench.import({
- extensions: ['jpm'],
- type: 'JPM Entity Part Model',
- multiple: true,
- }, function(files) {
- files.forEach(file => {
- var model = autoParseJSON(file.content)
- part_codec.parse(model, file.path)
- })
- })
- }
- })
})
})()
diff --git a/js/io/optifine_jpm.js b/js/io/optifine_jpm.js
new file mode 100644
index 000000000..bc107811d
--- /dev/null
+++ b/js/io/optifine_jpm.js
@@ -0,0 +1,232 @@
+(function() {
+
+var part_codec = new Codec('optifine_part', {
+ name: 'OptiFine Part',
+ extension: 'jpm',
+ remember: true,
+ compile(options) {
+ if (options === undefined) options = {}
+ var jpm = {}
+ if (textures[0]) {
+ jpm.texture = pathToName(textures[0].name, false)
+ }
+ jpm.textureSize = [Project.texture_width, Project.texture_height]
+
+ if (settings.credit.value) {
+ jpm.credit = settings.credit.value
+ }
+
+ var submodels = []
+ var boxes = []
+
+ Cube.all.forEach(function(s) {
+ if (s.export === false) return;
+ var box = {};
+ var originalOrigin = s.origin.slice();
+ s.transferOrigin([0, 0, 0])
+ box.coordinates = [
+ -s.to[0],
+ -s.from[1] - s.size(1),
+ s.from[2],
+ s.size(0),
+ s.size(1),
+ s.size(2)
+ ]
+ for (var face in s.faces) {
+ if (s.faces.hasOwnProperty(face)) {
+ if (s.faces[face].texture !== undefined && s.faces[face].texture !== null) {
+ box['uv'+capitalizeFirstLetter(face)] = [
+ Math.floor(s.faces[face].uv[0] / 16 * Project.texture_width),
+ Math.floor(s.faces[face].uv[1] / 16 * Project.texture_height),
+ Math.ceil(s.faces[face].uv[2] / 16 * Project.texture_width),
+ Math.ceil(s.faces[face].uv[3] / 16 * Project.texture_height)
+ ]
+ }
+ }
+ }
+ if (!s.rotation.equals([0, 0, 0])) {
+ var submodel = {
+ id: s.name,
+ rotate: [
+ -s.rotation[0],
+ -s.rotation[1],
+ s.rotation[2],
+ ],
+ boxes: [box],
+ }
+ submodels.push(submodel)
+ } else {
+ boxes.push(box)
+ }
+ s.transferOrigin(originalOrigin)
+ })
+ if (boxes.length) {
+ jpm.boxes = boxes
+ }
+ if (submodels.length) {
+ jpm.submodels = submodels
+ }
+
+
+ if (options.raw) {
+ return jpm
+ } else {
+ return autoStringify(jpm)
+ }
+ },
+ parse(model, path, add) {
+ Project.box_uv = false;
+ var new_cubes = [];
+ var import_group = add ? new Group({
+ name: pathToName(path)
+ }).init() : 'root';
+ var origin = [0, 0, 0];
+ Undo.initEdit({elements: new_cubes, outliner: true, uv_mode: true})
+
+ var resolution = model.textureSize;
+ if (resolution.length == 2) {
+ Project.texture_width = parseInt(resolution[0])||0;
+ Project.texture_height = parseInt(resolution[1])||0;
+ }
+ if (isApp) {
+ var texture_path = path.replace(/\.jpm$/, '.png')
+ if (model.texture) {
+ var arr = texture_path.split(osfs);
+ arr[arr.length-1] = model.texture
+ if (model.texture.substr(-4) != '.png') arr[arr.length-1] += '.png';
+ texture_path = arr.join(osfs);
+ }
+ if (fs.existsSync(texture_path)) {
+ new Texture().fromPath(texture_path).add(false)
+ }
+ }
+ function convertUVCoords(uv) {
+ if (uv instanceof Array && resolution instanceof Array) {
+ uv.forEach((n, i) => {
+ uv[i] *= 16 / resolution[i%2];
+ })
+ }
+ return uv;
+ }
+ function addSubmodel(submodel) {
+ if (submodel.boxes) {
+ submodel.boxes.forEach(function(box) {
+ var cs = box.coordinates
+ if (cs && cs.length >= 6) {
+ var rotation = submodel.rotate && [
+ -submodel.rotate[0],
+ -submodel.rotate[1],
+ submodel.rotate[2],
+ ]
+ base_cube = new Cube({
+ from: [
+ -cs[0]-cs[3],
+ -cs[1]-cs[4],
+ cs[2]
+ ],
+ size: [
+ cs[3],
+ cs[4],
+ cs[5]
+ ],
+ name: submodel.id,
+ rotation,
+ origin
+ })
+ if (box.uvNorth) {
+ if (!add) Project.box_uv = false;
+ base_cube.extend({
+ faces: {
+ north: {uv: convertUVCoords(box.uvNorth)},
+ east: {uv: convertUVCoords(box.uvEast)},
+ south: {uv: convertUVCoords(box.uvSouth)},
+ west: {uv: convertUVCoords(box.uvWest)},
+ up: {uv: convertUVCoords(box.uvUp)},
+ down: {uv: convertUVCoords(box.uvDown)},
+ }
+ })
+ } else {
+ if (!add) Project.box_uv = true;
+ base_cube.extend({
+ uv_offset: box.textureOffset
+ })
+ }
+
+ if (submodel.translate) {
+ base_cube.from[0] -= submodel.translate[0];
+ base_cube.from[1] -= submodel.translate[1];
+ base_cube.from[2] += submodel.translate[2];
+ base_cube.to[0] -= submodel.translate[0];
+ base_cube.to[1] -= submodel.translate[1];
+ base_cube.to[2] += submodel.translate[2];
+ base_cube.origin[0] -= submodel.translate[0];
+ base_cube.origin[1] -= submodel.translate[1];
+ base_cube.origin[2] += submodel.translate[2];
+ }
+
+ base_cube.init().addTo(import_group);
+ new_cubes.push(base_cube);
+ }
+ })
+ }
+ if (submodel.submodels) {
+ submodel.submodels.forEach(addSubmodel)
+ }
+ }
+ if (import_group instanceof Group) {
+ import_group.addTo()
+ }
+ Undo.finishEdit('add jpm model')
+ addSubmodel(model)
+ Canvas.updateAll()
+ }
+})
+
+
+var part_format = new ModelFormat({
+ name: 'OptiFine Part',
+ id: 'optifine_part',
+ extension: 'jpm',
+ icon: 'icon-format_optifine',
+ //show_on_start_screen: false,
+ single_texture: true,
+ integer_size: true,
+ rotate_cubes: true,
+ codec: part_codec
+})
+part_codec.format = part_format;
+
+
+
+BARS.defineActions(function() {
+ part_codec.export_action = new Action('export_optifine_part', {
+ name: 'Export OptiFine Part',
+ description: 'Export a single part for an OptiFine model',
+ icon: 'icon-optifine_file',
+ category: 'file',
+ condition: () => Format == part_format,
+ click: function () {
+ part_codec.export()
+ }
+ })
+ new Action('import_optifine_part', {
+ icon: 'icon-optifine_file',
+ category: 'file',
+ condition: () => (Format.id == 'optifine_entity' || Format.id == 'optifine_part'),
+ click: function () {
+ Blockbench.import({
+ extensions: ['jpm'],
+ type: 'JPM Entity Part Model',
+ multiple: true,
+ }, function(files) {
+ files.forEach(file => {
+ var model = autoParseJSON(file.content)
+ part_codec.parse(model, file.path, true)
+ })
+ })
+ }
+ })
+})
+
+
+})()
\ No newline at end of file
diff --git a/js/outliner/cube.js b/js/outliner/cube.js
index d1aeb69be..035a411b1 100644
--- a/js/outliner/cube.js
+++ b/js/outliner/cube.js
@@ -136,6 +136,11 @@ class Cube extends NonGroup {
Merge.number(this.to, object.to, 1)
Merge.number(this.to, object.to, 2)
}
+ if (object.size) {
+ if (typeof object.size[0] == 'number' && !isNaN(object.size[0])) this.to[0] = this.from[0] + object.size[0]
+ if (typeof object.size[1] == 'number' && !isNaN(object.size[1])) this.to[1] = this.from[1] + object.size[1]
+ if (typeof object.size[2] == 'number' && !isNaN(object.size[2])) this.to[2] = this.from[2] + object.size[2]
+ }
if (object.uv_offset) {
Merge.number(this.uv_offset, object.uv_offset, 0)
Merge.number(this.uv_offset, object.uv_offset, 1)
@@ -281,7 +286,7 @@ class Cube extends NonGroup {
if (!this.shade) el.shade = false;
if (this.inflate) el.inflate = this.inflate;
if (!this.rotation.allEqual(0)) el.rotation = this.rotation;
- if (!this.origin.allEqual(0)) el.origin = this.origin;
+ el.origin = this.origin;
if (!this.uv_offset.allEqual(0)) el.uv_offset = this.uv_offset;
if (!meta || !meta.box_uv) {
el.faces = {}
@@ -514,14 +519,14 @@ class Cube extends NonGroup {
} else {
var sides = faces
}
- var id = null
+ var value = null
if (texture) {
- id = texture.uuid
- } else if (texture === 'blank') {
- id = undefined;
+ value = texture.uuid
+ } else if (texture === false || texture === null) {
+ value = texture;
}
sides.forEach(function(side) {
- scope.faces[side].texture = id
+ scope.faces[side].texture = value
})
if (selected.indexOf(this) === 0) {
main_uv.loadData()
@@ -698,13 +703,13 @@ class Cube extends NonGroup {
}
return in_box;
}
- scale(val, axis, negative, absolute, allow_negative) {
+ resize(val, axis, negative, absolute, allow_negative) {
if (absolute) {
var before = 0;
} else {
var before = this.oldScale ? this.oldScale : this.size(axis);
}
- if (Format.integer_size) {
+ if (Format.integer_size && Project.box_uv) {
val = Math.ceil(val);
}
if (!negative) {
@@ -735,7 +740,7 @@ class Cube extends NonGroup {
Cube.prototype.type = 'cube';
Cube.prototype.icon = 'fa fa-cube';
Cube.prototype.movable = true;
- Cube.prototype.scalable = true;
+ Cube.prototype.resizable = true;
Cube.prototype.rotatable = true;
Cube.prototype.menu = new Menu([
'copy',
@@ -756,12 +761,12 @@ class Cube extends NonGroup {
var arr = [
{icon: 'crop_square', name: 'menu.cube.texture.blank', click: function(cube) {
cube.forSelected(function(obj) {
- obj.applyTexture('blank', true)
+ obj.applyTexture(false, true)
}, 'texture blank')
}},
{icon: 'clear', name: 'menu.cube.texture.transparent', click: function(cube) {
cube.forSelected(function(obj) {
- obj.applyTexture(undefined, true)
+ obj.applyTexture(null, true)
}, 'texture transparent')
}}
]
diff --git a/js/outliner/group.js b/js/outliner/group.js
index d01343c6b..89c1c7664 100644
--- a/js/outliner/group.js
+++ b/js/outliner/group.js
@@ -75,7 +75,7 @@ class Group extends OutlinerElement {
//Clear Old Group
if (Group.selected) Group.selected.unselect()
- if (event.shiftKey !== true && event.ctrlKey !== true) {
+ if (event.shiftKey !== true && event.ctrlOrCmd !== true) {
selected.length = 0
}
//Select This Group
@@ -83,7 +83,7 @@ class Group extends OutlinerElement {
s.selected = false
})
this.selected = true
- Group.selected = this
+ Group.selected = this;
//Select / Unselect Children
if (allSelected && event.which === 1) {
@@ -94,6 +94,11 @@ class Group extends OutlinerElement {
s.selectLow()
})
}
+ if (Animator.open) {
+ if (Animator.selected) {
+ Animator.selected.getBoneAnimator().select(true)
+ }
+ }
updateSelection()
return this;
}
@@ -134,6 +139,12 @@ class Group extends OutlinerElement {
}
unselect() {
if (this.selected === false) return;
+ if (Animator.open && Animator.selected) {
+ var ba = Animator.selected.animators[this.uuid];
+ if (ba) {
+ ba.selected = false
+ }
+ }
Group.selected = undefined;
this.selected = false
TickUpdates.selection = true;
@@ -256,7 +267,7 @@ class Group extends OutlinerElement {
obj.from[1] += shift.y;
obj.from[2] += shift.z;
}
- if (obj.scalable) {
+ if (obj.resizable) {
obj.to[0] += shift.x;
obj.to[1] += shift.y;
obj.to[2] += shift.z;
diff --git a/js/outliner/locator.js b/js/outliner/locator.js
index 87ba1848f..3be112f2d 100644
--- a/js/outliner/locator.js
+++ b/js/outliner/locator.js
@@ -73,8 +73,7 @@ Locator.selected = [];
Locator.all = [];
BARS.defineActions(function() {
- new Action({
- id: 'add_locator',
+ new Action('add_locator', {
icon: 'fa-anchor',
category: 'edit',
condition: () => {return Format.locators && Modes.edit},
diff --git a/js/outliner/outliner.js b/js/outliner/outliner.js
index 674be6289..6ee9b22e6 100644
--- a/js/outliner/outliner.js
+++ b/js/outliner/outliner.js
@@ -428,7 +428,7 @@ class NonGroup extends OutlinerElement {
})
//Control
- } else if (event && !Modes.paint && (event.ctrlKey || event.shiftKey )) {
+ } else if (event && !Modes.paint && (event.ctrlOrCmd || event.shiftKey )) {
if (selected.includes(scope)) {
selected = selected.filter(function(e) {
return e !== scope
@@ -890,8 +890,7 @@ onVueSetup(function() {
})
BARS.defineActions(function() {
- new Action({
- id: 'outliner_toggle',
+ new Action('outliner_toggle', {
icon: 'view_stream',
category: 'edit',
keybind: new Keybind({key: 115}),
@@ -907,8 +906,7 @@ BARS.defineActions(function() {
}
}
})
- new BarText({
- id: 'cube_counter',
+ new BarText('cube_counter', {
right: true,
click: function() {
@@ -951,8 +949,7 @@ BARS.defineActions(function() {
}
})
- new Action({
- id: 'sort_outliner',
+ new Action('sort_outliner', {
icon: 'sort_by_alpha',
category: 'edit',
click: function () {
@@ -964,8 +961,7 @@ BARS.defineActions(function() {
Undo.finishEdit('sort_outliner')
}
})
- new Action({
- id: 'local_move',
+ new Action('local_move', {
icon: 'check_box',
category: 'edit',
linked_setting: 'local_move',
@@ -974,8 +970,7 @@ BARS.defineActions(function() {
updateSelection()
}
})
- new Action({
- id: 'element_colors',
+ new Action('element_colors', {
icon: 'check_box',
category: 'edit',
linked_setting: 'outliner_colors',
@@ -984,8 +979,7 @@ BARS.defineActions(function() {
updateSelection()
}
})
- new Action({
- id: 'select_window',
+ new Action('select_window', {
icon: 'filter_list',
category: 'edit',
keybind: new Keybind({key: 70, ctrl: true}),
@@ -995,8 +989,7 @@ BARS.defineActions(function() {
$('#selgen_name').focus()
}
})
- new Action({
- id: 'invert_selection',
+ new Action('invert_selection', {
icon: 'swap_vert',
category: 'edit',
keybind: new Keybind({key: 73, ctrl: true}),
@@ -1014,8 +1007,7 @@ BARS.defineActions(function() {
Blockbench.dispatchEvent('invert_selection')
}
})
- new Action({
- id: 'select_all',
+ new Action('select_all', {
icon: 'select_all',
category: 'edit',
condition: () => Modes.edit || Modes.paint,
diff --git a/js/painter.js b/js/painter.js
index 65ecc6d32..537044b33 100644
--- a/js/painter.js
+++ b/js/painter.js
@@ -128,8 +128,8 @@ const Painter = {
var x = end_x;
var y = end_y;
var diff = {
- x: x - (Painter.current.x||x),
- y: y - (Painter.current.y||y),
+ x: x - (Painter.current.x == undefined ? x : Painter.current.x),
+ y: y - (Painter.current.y == undefined ? y : Painter.current.y),
}
var length = Math.sqrt(diff.x*diff.x + diff.y*diff.y)
if (new_face && !length) {
@@ -505,30 +505,47 @@ const Painter = {
id: 'add_bitmap',
title: tl('dialog.create_texture.title'),
form: {
- name: {label: 'dialog.create_texture.name', value: 'texture'},
- folder: {label: 'dialog.create_texture.folder'},
- entity_template:{label: 'dialog.create_texture.template', type: 'checkbox', condition: Cube.all.length},
- compress: {label: 'dialog.create_texture.compress', type: 'checkbox', value: true},
- power: {label: 'dialog.create_texture.power', type: 'checkbox', value: true},
- double_use: {label: 'dialog.create_texture.double_use', type: 'checkbox', value: true, condition: Project.box_uv},
- color: {type: 'color', colorpicker: Painter.background_color},
- resolution: {label: 'dialog.create_texture.resolution', type: 'number', value: 16, min: 16, max: 2048},
+ name: {label: 'generic.name', value: 'texture'},
+ folder: {label: 'dialog.create_texture.folder'},
+ template: {label: 'dialog.create_texture.template', type: 'checkbox', condition: Cube.all.length},
+ color: {type: 'color', colorpicker: Painter.background_color},
+ resolution: {label: 'dialog.create_texture.resolution', type: 'number', value: 16, min: 16, max: 2048},
},
onConfirm: function(results) {
results.particle = 'auto';
- Painter.addBitmap(results)
dialog.hide()
+ if (results.template) {
+ var dialog2 = new Dialog({
+ id: 'texture_template',
+ title: tl('dialog.create_texture.template'),
+ form: {
+ compress: {label: 'dialog.create_texture.compress', type: 'checkbox', value: true},
+ power: {label: 'dialog.create_texture.power', type: 'checkbox', value: true},
+ double_use: {label: 'dialog.create_texture.double_use', type: 'checkbox', value: true, condition: Project.box_uv},
+ color: {type: 'color', colorpicker: Painter.background_color},
+ resolution: {label: 'dialog.create_texture.resolution', type: 'select', value: 16, options: {
+ 16: '16',
+ 32: '32',
+ 64: '64',
+ 128: '128',
+ 256: '256',
+ 512: '512',
+ }},
+ },
+ onConfirm: function(results2) {
+ $.extend(results, results2)
+ Painter.addBitmap(results)
+ dialog2.hide()
+ }
+ }).show()
+ if (Painter.background_color.get().toHex8() === 'ffffffff') {
+ Painter.background_color.set('#00000000')
+ }
+ } else {
+ Painter.addBitmap(results)
+ }
}
}).show()
- $('#add_bitmap #compress, #add_bitmap #power, #add_bitmap #double_use').parent().hide()
-
- $('.dialog#add_bitmap input#entity_template').click(function() {
- var checked = $('.dialog#add_bitmap input#entity_template').is(':checked')
- $('#add_bitmap #compress, #add_bitmap #power, #add_bitmap #double_use').parent()[ checked ? 'show' : 'hide' ]()
- if (Painter.background_color.get().toHex8() === 'ffffffff') {
- Painter.background_color.set('#00000000')
- }
- })
},
addBitmap(options, after) {
if (typeof options !== 'object') {
@@ -563,12 +580,12 @@ const Painter = {
if (typeof after === 'function') {
after(texture)
}
- if (!options.entity_template) {
+ if (!options.template) {
Undo.finishEdit('create blank texture', {textures: [texture], bitmap: true})
}
return texture;
}
- if (options.entity_template === true) {
+ if (options.template === true) {
Undo.initEdit({
textures: Format.single_texture ? textures : [],
elements: Format.single_texture ? Cube.all : Cube.selected,
@@ -941,8 +958,7 @@ const ColorPanel = {
BARS.defineActions(function() {
- new Tool({
- id: 'brush_tool',
+ new Tool('brush_tool', {
icon: 'fa-paint-brush',
category: 'tools',
toolbar: 'brush',
@@ -964,8 +980,7 @@ BARS.defineActions(function() {
$('.UVEditor').find('#uv_size').show()
}
})
- new Tool({
- id: 'fill_tool',
+ new Tool('fill_tool', {
icon: 'format_color_fill',
category: 'tools',
toolbar: 'brush',
@@ -986,8 +1001,7 @@ BARS.defineActions(function() {
$('.UVEditor').find('#uv_size').show()
}
})
- new Tool({
- id: 'eraser',
+ new Tool('eraser', {
icon: 'fa-eraser',
category: 'tools',
toolbar: 'brush',
@@ -1007,8 +1021,7 @@ BARS.defineActions(function() {
$('.UVEditor').find('#uv_size').show()
}
})
- new Tool({
- id: 'color_picker',
+ new Tool('color_picker', {
icon: 'colorize',
category: 'tools',
toolbar: 'brush',
@@ -1029,8 +1042,7 @@ BARS.defineActions(function() {
}
})
- new BarSelect({
- id: 'brush_mode',
+ new BarSelect('brush_mode', {
condition: () => Toolbox && (Toolbox.selected.id === 'brush_tool' || Toolbox.selected.id === 'eraser'),
onChange() {
BARS.updateConditions();
@@ -1041,8 +1053,7 @@ BARS.defineActions(function() {
noise: true
}
})
- new BarSelect({
- id: 'fill_mode',
+ new BarSelect('fill_mode', {
condition: () => Toolbox && Toolbox.selected.id === 'fill_tool',
options: {
face: true,
@@ -1052,8 +1063,7 @@ BARS.defineActions(function() {
})
- new Action({
- id: 'painting_grid',
+ new Action('painting_grid', {
name: tl('settings.painting_grid'),
description: tl('settings.painting_grid.desc'),
icon: 'check_box',
@@ -1068,24 +1078,22 @@ BARS.defineActions(function() {
}
})
- new NumSlider({
- id: 'slider_brush_size',
+ new NumSlider('slider_brush_size', {
condition: () => (Toolbox && ['brush_tool', 'eraser'].includes(Toolbox.selected.id)),
settings: {
min: 1, max: 20, interval: 1, default: 1,
}
})
- new NumSlider({
- id: 'slider_brush_softness',
+ new NumSlider('slider_brush_softness', {
condition: () => (Toolbox && ['brush_tool', 'eraser'].includes(Toolbox.selected.id)),
settings: {
min: 0, max: 100, default: 0,
interval: function(event) {
- if (event.shiftKey && event.ctrlKey) {
+ if (event.shiftKey && event.ctrlOrCmd) {
return 0.25;
} else if (event.shiftKey) {
return 5;
- } else if (event.ctrlKey) {
+ } else if (event.ctrlOrCmd) {
return 1;
} else {
return 10;
@@ -1093,17 +1101,16 @@ BARS.defineActions(function() {
}
}
})
- new NumSlider({
- id: 'slider_brush_opacity',
+ new NumSlider('slider_brush_opacity', {
condition: () => (Toolbox && ['brush_tool', 'eraser'].includes(Toolbox.selected.id)),
settings: {
min: 0, max: 100, default: 100,
interval: function(event) {
- if (event.shiftKey && event.ctrlKey) {
+ if (event.shiftKey && event.ctrlOrCmd) {
return 0.25;
} else if (event.shiftKey) {
return 5;
- } else if (event.ctrlKey) {
+ } else if (event.ctrlOrCmd) {
return 1;
} else {
return 10;
@@ -1111,17 +1118,16 @@ BARS.defineActions(function() {
}
}
})
- new NumSlider({
- id: 'slider_brush_min_opacity',
+ new NumSlider('slider_brush_min_opacity', {
condition: () => (Toolbox && ['brush_tool', 'eraser'].includes(Toolbox.selected.id) && BarItems.brush_mode.value == 'noise'),
settings: {
min: 0, max: 100, default: 50,
interval: function(event) {
- if (event.shiftKey && event.ctrlKey) {
+ if (event.shiftKey && event.ctrlOrCmd) {
return 0.25;
} else if (event.shiftKey) {
return 5;
- } else if (event.ctrlKey) {
+ } else if (event.ctrlOrCmd) {
return 1;
} else {
return 10;
diff --git a/js/plugin_loader.js b/js/plugin_loader.js
index 14ddc9174..899f90d61 100644
--- a/js/plugin_loader.js
+++ b/js/plugin_loader.js
@@ -246,6 +246,7 @@ Plugin.register = function(id, data) {
}
if (!plugin) return;
plugin.extend(data)
+ if (data.icon) plugin.icon = Blockbench.getIconNode(data.icon)
if (plugin.onload instanceof Function) {
plugin.onload()
}
@@ -358,7 +359,7 @@ function loadPluginFromFile(file) {
var plugin = new Plugin().loadFromFile(file, true)
}
function switchPluginTabs(installed) {
- $('#plugins .tab').removeClass('open')
+ $('#plugins .tab_bar > .open').removeClass('open')
if (installed) {
$('#installed_plugins').addClass('open')
Plugins.Vue._data.showAll = false
@@ -369,8 +370,7 @@ function switchPluginTabs(installed) {
}
BARS.defineActions(function() {
- new Action({
- id: 'plugins_window',
+ new Action('plugins_window', {
icon: 'extension',
category: 'blockbench',
click: function () {
@@ -378,8 +378,7 @@ BARS.defineActions(function() {
$('#plugin_list').css('max-height', limitNumber($(window).height()-300, 80, 600)+'px')
}
})
- new Action({
- id: 'reload_plugins',
+ new Action('reload_plugins', {
icon: 'sync',
category: 'blockbench',
keybind: new Keybind({ctrl: true, key: 74}),
@@ -387,8 +386,7 @@ BARS.defineActions(function() {
Plugins.devReload()
}
})
- new Action({
- id: 'load_plugin',
+ new Action('load_plugin', {
icon: 'fa-file-code',
category: 'blockbench',
click: function () {
diff --git a/js/preview/canvas.js b/js/preview/canvas.js
index cb62c3796..1993d9946 100644
--- a/js/preview/canvas.js
+++ b/js/preview/canvas.js
@@ -99,9 +99,7 @@ const Canvas = {
Canvas.clear()
Canvas.updateAllBones()
Cube.all.forEach(function(s) {
- if (s.visibility == true) {
- Canvas.addCube(s)
- }
+ Canvas.addCube(s)
})
updateSelection()
},
@@ -114,6 +112,14 @@ const Canvas = {
},
updateVisibility() {
Cube.all.forEach(function(s) {
+ s.mesh.visible = s.visibility == true;
+ if (s.visibility) {
+ Canvas.adaptObjectFaces(s, s.mesh)
+ if (!Prop.wireframe) {
+ Canvas.updateUV(s);
+ }
+ }
+ /*
var mesh = s.mesh
if (s.visibility == true) {
if (!mesh) {
@@ -128,7 +134,7 @@ const Canvas = {
}
} else if (mesh && mesh.parent) {
mesh.parent.remove(mesh)
- }
+ }*/
})
updateSelection()
},
@@ -192,9 +198,7 @@ const Canvas = {
if (mesh && mesh.parent) {
mesh.parent.remove(mesh)
}
- if (obj.visibility == true) {
- Canvas.addCube(obj)
- }
+ Canvas.addCube(obj)
})
updateSelection()
},
@@ -257,7 +261,7 @@ const Canvas = {
Group.all.forEach((obj) => {
let mesh = obj.mesh
- if (obj.visibility && mesh) {
+ if (mesh) {
mesh.rotation.reorder('ZYX')
obj.rotation.forEach(function(n, i) {
@@ -347,9 +351,7 @@ const Canvas = {
}
Canvas.buildOutline(obj)
},
- adaptObjectPosition(cube, mesh, parent) {
- if (!cube.visibility) return;
-
+ adaptObjectPosition(cube, mesh, parent) {
if (!mesh || mesh > 0) mesh = cube.mesh
var from = cube.from.slice()
@@ -398,7 +400,7 @@ const Canvas = {
} else {
scene.add(mesh)
}
- } else {
+ } else if (mesh.parent !== scene) {
scene.add(mesh)
}
if (Modes.paint) {
diff --git a/js/preview/preview.js b/js/preview/preview.js
index 2b5930fb5..7cd91f1d6 100644
--- a/js/preview/preview.js
+++ b/js/preview/preview.js
@@ -14,7 +14,8 @@ const rot_origin = new THREE.Object3D();
var gizmo_colors = {
r: new THREE.Color(0xfd3043),
g: new THREE.Color(0x26ec45),
- b: new THREE.Color(0x2d5ee8)
+ b: new THREE.Color(0x2d5ee8),
+ outline: new THREE.Color()
}
class Preview {
@@ -66,7 +67,7 @@ class Preview {
this.loadBackground()
this.selection = {
- box: $('
')
+ box: $('
')
}
this.raycaster = new THREE.Raycaster()
@@ -321,7 +322,10 @@ class Preview {
if (Toolbox.selected.selectFace) {
main_uv.setFace(data.face, false)
}
- Blockbench.dispatchEvent( 'canvas_select', data )
+ Blockbench.dispatchEvent('canvas_select', data)
+ if (Modes.paint) {
+ event = 0;
+ }
if (Format.bone_rig && (
Animator.open ||
(!Format.rotate_cubes && ['rotate_tool', 'pivot_tool'].includes(Toolbox.selected.id)) ||
@@ -382,7 +386,7 @@ class Preview {
//Selection Rectangle
startSelRect(event) {
var scope = this;
- if (!display_mode || this.movingBackground) {
+ if (Modes.edit || this.movingBackground) {
this.sr_move_f = function(event) { scope.moveSelRect(event)}
this.sr_stop_f = function(event) { scope.stopSelRect(event)}
this.canvas.addEventListener('mousemove', this.sr_move_f, false)
@@ -402,7 +406,7 @@ class Preview {
}
return
};
- if (display_mode) return;
+ if (!Modes.edit) return;
$(this.canvas).parent().append(this.selection.box)
this.selection.activated = settings.canvas_unselect.value;
@@ -462,7 +466,7 @@ class Preview {
unselectAll()
elements.forEach(function(cube) {
- if ((event.shiftKey || event.ctrlKey) && scope.selection.old_selected.indexOf(cube) >= 0) {
+ if ((event.shiftKey || event.ctrlOrCmd) && scope.selection.old_selected.indexOf(cube) >= 0) {
var isSelected = true
} else {
if (cube instanceof Cube && cube.visibility && cube.mesh) {
@@ -619,6 +623,8 @@ class Preview {
var scope = this;
function editVis(edit) {
edit(three_grid)
+ edit(Canvas.side_grids.x)
+ edit(Canvas.side_grids.z)
edit(Transformer)
edit(outlines)
edit(rot_origin)
@@ -745,7 +751,7 @@ class Preview {
}}
]
}},
- {icon: 'videocam', name: 'menu.preview.perspective', condition: function(preview) {return !preview.movingBackground && !display_mode && !Animator.open}, children: function(preview) {
+ {icon: 'videocam', name: 'menu.preview.perspective', condition: function(preview) {return !preview.movingBackground && !Modes.display}, children: function(preview) {
function getBtn(angle, pers) {
var condition = (pers && !preview.isOrtho)
|| (!pers && angle === preview.angle && preview.isOrtho);
@@ -762,10 +768,10 @@ class Preview {
{icon: getBtn(5), name: 'direction.west', color: 'x', click: function(preview) {preview.setOrthographicCamera(5)}}
]
}},
- {icon: 'widgets', name: 'menu.preview.quadview', condition: function(preview) {return !quad_previews.enabled && !preview.movingBackground && !display_mode && !Animator.open}, click: function() {
+ {icon: 'widgets', name: 'menu.preview.quadview', condition: function(preview) {return !quad_previews.enabled && !preview.movingBackground && !Modes.display && !Animator.open}, click: function() {
openQuadView()
}},
- {icon: 'web_asset', name: 'menu.preview.fullview', condition: function(preview) {return quad_previews.enabled && !preview.movingBackground && !display_mode}, click: function(preview) {
+ {icon: 'web_asset', name: 'menu.preview.fullview', condition: function(preview) {return quad_previews.enabled && !preview.movingBackground && !Modes.display}, click: function(preview) {
preview.fullscreen()
}},
{icon: 'cancel', color: 'x', name: 'menu.preview.stop_drag', condition: function(preview) {return preview.movingBackground;}, click: function(preview) {
@@ -1309,8 +1315,7 @@ function buildGrid() {
}
BARS.defineActions(function() {
- new Action({
- id: 'toggle_wireframe',
+ new Action('toggle_wireframe', {
icon: 'border_clear',
category: 'view',
keybind: new Keybind({key: 90}),
@@ -1325,15 +1330,13 @@ BARS.defineActions(function() {
}
})
- new Action({
- id: 'screenshot_model',
+ new Action('screenshot_model', {
icon: 'fa-cubes',
category: 'view',
keybind: new Keybind({key: 80, ctrl: true}),
click: function () {quad_previews.current.screenshot()}
})
- new Action({
- id: 'record_model_gif',
+ new Action('record_model_gif', {
icon: 'local_movies',
category: 'view',
click: function () {
@@ -1363,25 +1366,22 @@ BARS.defineActions(function() {
}).show()
}
})
- new Action({
- id: 'screenshot_app',
+ new Action('screenshot_app', {
icon: 'icon-bb_interface',
category: 'view',
condition: isApp,
click: function () {Screencam.fullScreen()}
})
- new Action({
- id: 'toggle_quad_view',
+ new Action('toggle_quad_view', {
icon: 'widgets',
category: 'view',
- condition: () => (Modes.id === 'edit' || Modes.id === 'paint'),
+ condition: () => !Modes.display,
keybind: new Keybind({key: 9}),
click: function () {
main_preview.toggleFullscreen()
}
})
- new Action({
- id: 'camera_reset',
+ new Action('camera_reset', {
name: 'menu.preview.perspective.reset',
description: 'menu.preview.perspective.reset',
icon: 'videocam',
@@ -1391,8 +1391,7 @@ BARS.defineActions(function() {
quad_previews.current.resetCamera()
}
})
- new Action({
- id: 'camera_normal',
+ new Action('camera_normal', {
name: 'menu.preview.perspective.normal',
description: 'menu.preview.perspective.normal',
icon: 'videocam',
@@ -1404,8 +1403,7 @@ BARS.defineActions(function() {
}
})
- new Action({
- id: 'camera_top',
+ new Action('camera_top', {
name: 'direction.top',
description: 'direction.top',
icon: 'videocam',
@@ -1417,8 +1415,7 @@ BARS.defineActions(function() {
quad_previews.current.setOrthographicCamera(0)
}
})
- new Action({
- id: 'camera_bottom',
+ new Action('camera_bottom', {
name: 'direction.bottom',
description: 'direction.bottom',
icon: 'videocam',
@@ -1430,8 +1427,7 @@ BARS.defineActions(function() {
quad_previews.current.setOrthographicCamera(1)
}
})
- new Action({
- id: 'camera_south',
+ new Action('camera_south', {
name: 'direction.south',
description: 'direction.south',
icon: 'videocam',
@@ -1443,8 +1439,7 @@ BARS.defineActions(function() {
quad_previews.current.setOrthographicCamera(2)
}
})
- new Action({
- id: 'camera_north',
+ new Action('camera_north', {
name: 'direction.north',
description: 'direction.north',
icon: 'videocam',
@@ -1456,8 +1451,7 @@ BARS.defineActions(function() {
quad_previews.current.setOrthographicCamera(3)
}
})
- new Action({
- id: 'camera_east',
+ new Action('camera_east', {
name: 'direction.east',
description: 'direction.east',
icon: 'videocam',
@@ -1469,8 +1463,7 @@ BARS.defineActions(function() {
quad_previews.current.setOrthographicCamera(4)
}
})
- new Action({
- id: 'camera_west',
+ new Action('camera_west', {
name: 'direction.west',
description: 'direction.west',
icon: 'videocam',
diff --git a/js/preview/transformer.js b/js/preview/transformer.js
index 94b108a46..c1fbab5dd 100644
--- a/js/preview/transformer.js
+++ b/js/preview/transformer.js
@@ -558,6 +558,16 @@
this.add( gizmoObj );
}
+ this.pivot_marker = new THREE.Mesh(
+ new THREE.IcosahedronGeometry(0.08),
+ new THREE.MeshBasicMaterial()
+ )
+ this.pivot_marker.material.depthTest = false;
+ this.pivot_marker.material.depthWrite = false;
+ this.pivot_marker.material.side = THREE.FrontSide;
+ this.pivot_marker.material.transparent = true;
+ this.pivot_marker.material.color = gizmo_colors.outline;
+ this.children[0].add(this.pivot_marker)
//Adjust GIzmos
this.traverse((kid) => {
@@ -630,11 +640,6 @@
var camPosition = new THREE.Vector3();
var camRotation = new THREE.Euler();
- var animation_channels = {
- rotate: 'rotation',
- translate: 'position',
- scale: 'scale'
- }
this.attach = function ( object ) {
this.elements.safePush(object);
@@ -654,6 +659,9 @@
_mode = mode||_mode;
if ( _mode === "scale" ) scope.space = "local";
for ( var type in _gizmo ) _gizmo[ type ].visible = (type === _mode);
+ if (mode == 'translate') {
+ this.pivot_marker.visible = Toolbox.selected.visible = Toolbox.selected.id == 'pivot_tool';
+ }
this.update();
scope.dispatchEvent( changeEvent );
@@ -734,7 +742,7 @@
} else {
worldRotation.set(0, 0, 0);
this.rotation.set(0, 0, 0);
- _gizmo[ _mode ].update( new THREE.Euler(), eye );
+ _gizmo[ _mode ].update( worldRotation, eye );
}
_gizmo[ _mode ].highlight( scope.axis );
};
@@ -796,7 +804,7 @@
selected.forEach(element => {
if (
(element.movable && Toolbox.selected.transformerMode == 'translate') ||
- (element.scalable && Toolbox.selected.transformerMode == 'scale') ||
+ (element.resizable && Toolbox.selected.transformerMode == 'scale') ||
(element.rotatable && Toolbox.selected.transformerMode == 'rotate')
) {
scope.attach(element);
@@ -957,7 +965,7 @@
if (Toolbox.selected.id === 'resize_tool') {
var axisnr = getAxisNumber(scope.axis.toLowerCase().replace('n', ''))
selected.forEach(function(obj) {
- if (obj.scalable) {
+ if (obj.resizable) {
obj.oldScale = obj.size(axisnr)
}
})
@@ -978,18 +986,20 @@
if (Timeline.playing) {
Timeline.pause()
}
- var channel = animation_channels[_mode]
scope.keyframe = false;
- for (var i = 0; i < Timeline.keyframes.length; i++) {
- var kf = Timeline.keyframes[i];
- if (kf.channel === channel && Math.abs(kf.time - Timeline.second) < 0.02) {
- scope.keyframe = kf
+ var animator = Animator.selected.getBoneAnimator();
+ if (animator) {
+ var channel = Toolbox.selected.animation_channel;
+ var all = animator[channel];
+ for (var kf of all) {
+ if (Math.abs(kf.time - Timeline.time) < 0.02) {
+ scope.keyframe = kf
+ }
+ }
+ Undo.initEdit({keyframes: scope.keyframe ? [scope.keyframe] : []})
+ if (!scope.keyframe) {
+ scope.keyframe = animator.createKeyframe(null, Timeline.time, channel);
}
- }
- Undo.initEdit({keyframes: scope.keyframe ? [scope.keyframe] : []})
- if (!scope.keyframe) {
- var ba = Animator.selected.getBoneAnimator()
- scope.keyframe = ba.addKeyframe(null, Timeline.second, channel);
}
} else if (Modes.id === 'display') {
@@ -1016,7 +1026,9 @@
if (Toolbox.selected.transformerMode !== 'rotate') {
point.sub( offset );
- point.removeEuler(worldRotation)
+ if (!display_mode) {
+ point.removeEuler(worldRotation)
+ }
} else {
point.sub( worldPosition );
@@ -1033,7 +1045,7 @@
if (Toolbox.selected.id === 'move_tool') {
- var snap_factor = canvasGridSize(event.shiftKey, event.ctrlKey)
+ var snap_factor = canvasGridSize(event.shiftKey, event.ctrlOrCmd)
point[axis] = Math.round( point[axis] / snap_factor ) * snap_factor;
if (previousValue === undefined) {
@@ -1047,7 +1059,7 @@
var overlapping = false
if (Format.canvas_limit) {
selected.forEach(function(obj) {
- if (obj.movable && obj.scalable) {
+ if (obj.movable && obj.resizable) {
overlapping = overlapping || (
obj.to[axisNumber] + difference + obj.inflate > 32 ||
obj.to[axisNumber] + difference + obj.inflate < -16 ||
@@ -1076,7 +1088,7 @@
}
} else if (Toolbox.selected.id === 'resize_tool') {
//Scale
- var snap_factor = canvasGridSize(event.shiftKey, event.ctrlKey)
+ var snap_factor = canvasGridSize(event.shiftKey, event.ctrlOrCmd)
point[axis] = Math.round( point[axis] / snap_factor ) * snap_factor;
@@ -1084,8 +1096,8 @@
beforeFirstChange(event)
selected.forEach(function(obj, i) {
- if (obj.scalable) {
- obj.scale(point[axis], axisNumber, !scope.direction)
+ if (obj.resizable) {
+ obj.resize(point[axis], axisNumber, !scope.direction)
}
})
scope.updateSelection()
@@ -1112,7 +1124,7 @@
}
} else if (Toolbox.selected.id === 'pivot_tool') {
- var snap_factor = canvasGridSize(event.shiftKey, event.ctrlKey)
+ var snap_factor = canvasGridSize(event.shiftKey, event.ctrlOrCmd)
point[axis] = Math.round( point[axis] / snap_factor ) * snap_factor;
if (previousValue === undefined) {
@@ -1165,7 +1177,7 @@
value *= (scope.direction) ? 0.1 : -0.1
round_num = 0.1
} else {
- var round_num = canvasGridSize(event.shiftKey, event.ctrlKey)
+ var round_num = canvasGridSize(event.shiftKey, event.ctrlOrCmd)
}
}
value = Math.round(value/round_num)*round_num
@@ -1195,7 +1207,7 @@
scope.getWorldQuaternion(rotation)
point.applyQuaternion(rotation.inverse())
- var channel = animation_channels[_mode]
+ var channel = Toolbox.selected.animation_channel
if (channel === 'position') channel = 'translation';
var value = point[axis]
var bf = display[display_slot][channel][axisNumber] - (previousValue||0)
diff --git a/js/textures.js b/js/textures.js
index 7fb0c2eab..d080e83b0 100644
--- a/js/textures.js
+++ b/js/textures.js
@@ -711,7 +711,7 @@ class Texture {
})
} else {
var find_path;
- if (Format.single_texture) {
+ if (Format.bone_rig && Project.geometry_name) {
find_path = findEntityTexture(Project.geometry_name, true)
}
if (!find_path && ModelMeta.export_path) {
@@ -1037,8 +1037,7 @@ onVueSetup(function() {
})
BARS.defineActions(function() {
- new Action({
- id: 'import_texture',
+ new Action('import_texture', {
icon: 'library_add',
category: 'textures',
keybind: new Keybind({key: 84, ctrl: true}),
@@ -1072,8 +1071,7 @@ BARS.defineActions(function() {
})
}
})
- new Action({
- id: 'create_texture',
+ new Action('create_texture', {
icon: 'icon-create_bitmap',
category: 'textures',
keybind: new Keybind({key: 84, ctrl: true, shift: true}),
@@ -1081,15 +1079,13 @@ BARS.defineActions(function() {
Painter.addBitmapDialog()
}
})
- new Action({
- id: 'save_textures',
+ new Action('save_textures', {
icon: 'save',
category: 'textures',
keybind: new Keybind({key: 83, ctrl: true, alt: true}),
click: function () {saveTextures()}
})
- new Action({
- id: 'change_textures_folder',
+ new Action('change_textures_folder', {
icon: 'fas fa-hdd',
category: 'textures',
condition: () => textures.length > 0,
@@ -1126,8 +1122,7 @@ BARS.defineActions(function() {
})
}
})
- new Action({
- id: 'animated_textures',
+ new Action('animated_textures', {
icon: 'play_arrow',
category: 'textures',
condition: function() {
diff --git a/js/transform.js b/js/transform.js
index 0f69fe690..982d9bd81 100644
--- a/js/transform.js
+++ b/js/transform.js
@@ -35,16 +35,16 @@ function getSelectionCenter() {
var i = 0;
selected.forEach(cube => {
var m = cube.mesh
- if (cube.visibility && m) {
+ if (m) {
var pos = cube.getWorldCenter()
center[0] += pos.x
center[1] += pos.y
center[2] += pos.z
} else if (!m && cube.from) {
- center[0] += cube.from[0];
- center[1] += cube.from[1];
- center[2] += cube.from[2];
+ center[0] += cube.from[0]-scene.position.x;
+ center[1] += cube.from[1]-scene.position.y;
+ center[2] += cube.from[2]-scene.position.z;
}
})
for (var i = 0; i < 3; i++) {
@@ -143,7 +143,7 @@ function moveCubesRelative(difference, index, event) { //Multiple
if (index === 1 && height === 'up') difference *= -1
if (event) {
- difference *= canvasGridSize(event.shiftKey, event.ctrlKey);
+ difference *= canvasGridSize(event.shiftKey, event.ctrlOrCmd);
}
Cube.selected.forEach(cube => {
@@ -173,41 +173,52 @@ function rotateSelected(axis, steps) {
}
//Mirror
function mirrorSelected(axis) {
- if (!Cube.selected.length) return;
- Undo.initEdit({elements: Cube.selected, outliner: Format.bone_rig})
- var center = 8
- if (Format.bone_rig) {
- center = 0
- if (Group.selected && Group.selected.matchesSelection()) {
- function flipGroup(group) {
- if (group.type === 'group') {
- for (var i = 0; i < 3; i++) {
- if (i === axis) {
- group.origin[i] *= -1
- } else {
- group.rotation[i] *= -1
+ if (Modes.animate && Timeline.selected.length) {
+
+ Undo.initEdit({keyframes: Timeline.selected})
+ for (var kf of Timeline.selected) {
+ kf.flip(axis)
+ }
+ Undo.finishEdit('flipped keyframes');
+ updateKeyframeSelection();
+ Animator.preview();
+
+ } else if (Modes.edit && Cube.selected.length) {
+ Undo.initEdit({elements: Cube.selected, outliner: Format.bone_rig})
+ var center = 8
+ if (Format.bone_rig) {
+ center = 0
+ if (Group.selected && Group.selected.matchesSelection()) {
+ function flipGroup(group) {
+ if (group.type === 'group') {
+ for (var i = 0; i < 3; i++) {
+ if (i === axis) {
+ group.origin[i] *= -1
+ } else {
+ group.rotation[i] *= -1
+ }
+ }
+ if (axis == 0 && group.name.includes('right')) {
+ group.name = group.name.replace(/right/g, 'left').replace(/2/, '');
+ } else if (axis == 0 && group.name.includes('left')) {
+ group.name = group.name.replace(/left/g, 'right').replace(/2/, '');
}
- }
- if (axis == 0 && group.name.includes('right')) {
- group.name = group.name.replace(/right/g, 'left').replace(/2/, '');
- } else if (axis == 0 && group.name.includes('left')) {
- group.name = group.name.replace(/left/g, 'right').replace(/2/, '');
}
}
+ flipGroup(Group.selected)
+ Group.selected.forEachChild(flipGroup)
}
- flipGroup(Group.selected)
- Group.selected.forEachChild(flipGroup)
}
+ Cube.selected.forEach(function(obj) {
+ obj.flip(axis, center, false)
+ if (Project.box_uv && axis === 0) {
+ obj.shade = !obj.shade
+ Canvas.updateUV(obj)
+ }
+ })
+ updateSelection()
+ Undo.finishEdit('mirror')
}
- Cube.selected.forEach(function(obj) {
- obj.flip(axis, center, false)
- if (Project.box_uv && axis === 0) {
- obj.shade = !obj.shade
- Canvas.updateUV(obj)
- }
- })
- updateSelection()
- Undo.finishEdit('mirror')
}
const Vertexsnap = {
@@ -355,9 +366,6 @@ const Vertexsnap = {
obj.origin[0] += cube_pos.getComponent(0)
obj.origin[1] += cube_pos.getComponent(1)
obj.origin[2] += cube_pos.getComponent(2)
- } else {
- var q = obj.mesh.getWorldQuaternion(new THREE.Quaternion()).inverse()
- cube_pos.applyQuaternion(q)
}
var in_box = obj.move(cube_pos);
if (!in_box) {
@@ -506,14 +514,14 @@ function centerCubes(axis, update) {
var average = 0;
selected.forEach(function(obj) {
if (obj.movable) average += obj.from[axis]
- if (obj.scalable) average += obj.to[axis]
+ if (obj.resizable) average += obj.to[axis]
})
average = average / (selected.length * 2)
var difference = (Format.bone_rig ? 0 : 8) - average
selected.forEach(function(obj) {
if (obj.movable) obj.from[axis] = limitToBox(obj.from[axis] + difference, obj.inflate);
- if (obj.scalable) obj.to[axis] = limitToBox(obj.to[axis] + difference, obj.inflate);
+ if (obj.resizable) obj.to[axis] = limitToBox(obj.to[axis] + difference, obj.inflate);
if (obj.origin) obj.origin[axis] += difference;
})
@@ -525,11 +533,11 @@ function centerCubes(axis, update) {
function getRotationInterval(event) {
if (Format.rotation_limit) {
return 22.5;
- } else if (event.shiftKey && event.ctrlKey) {
+ } else if (event.shiftKey && event.ctrlOrCmd) {
return 0.25;
} else if (event.shiftKey) {
return 45;
- } else if (event.ctrlKey) {
+ } else if (event.ctrlOrCmd) {
return 1;
} else {
return 5;
@@ -628,8 +636,7 @@ BARS.defineActions(function() {
}
})
}
- new NumSlider({
- id: 'slider_pos_x',
+ new NumSlider('slider_pos_x', {
condition: () => (selected.length && Modes.edit),
get: function() {
return selected[0].from[0]
@@ -644,8 +651,7 @@ BARS.defineActions(function() {
Undo.finishEdit('move')
}
})
- new NumSlider({
- id: 'slider_pos_y',
+ new NumSlider('slider_pos_y', {
condition: () => (selected.length && Modes.edit),
get: function() {
return selected[0].from[1]
@@ -660,8 +666,7 @@ BARS.defineActions(function() {
Undo.finishEdit('move')
}
})
- new NumSlider({
- id: 'slider_pos_z',
+ new NumSlider('slider_pos_z', {
condition: () => (selected.length && Modes.edit),
get: function() {
return selected[0].from[2]
@@ -678,21 +683,20 @@ BARS.defineActions(function() {
})
- function scaleOnAxis(value, fixed, axis) {
+ function resizeOnAxis(value, fixed, axis) {
selected.forEach(function(obj, i) {
- if (obj.scalable) {
- obj.scale(value, axis, false, fixed, true)
+ if (obj.resizable) {
+ obj.resize(value, axis, false, fixed, true)
}
})
}
- new NumSlider({
- id: 'slider_size_x',
+ new NumSlider('slider_size_x', {
condition: () => (Cube.selected.length && Modes.edit),
get: function() {
return Cube.selected[0].to[0] - Cube.selected[0].from[0]
},
change: function(value, fixed) {
- scaleOnAxis(value, fixed, 0)
+ resizeOnAxis(value, fixed, 0)
},
onBefore: function() {
Undo.initEdit({elements: Cube.selected})
@@ -701,14 +705,13 @@ BARS.defineActions(function() {
Undo.finishEdit('resize')
}
})
- new NumSlider({
- id: 'slider_size_y',
+ new NumSlider('slider_size_y', {
condition: () => (Cube.selected.length && Modes.edit),
get: function() {
return Cube.selected[0].to[1] - Cube.selected[0].from[1]
},
change: function(value, fixed) {
- scaleOnAxis(value, fixed, 1)
+ resizeOnAxis(value, fixed, 1)
},
onBefore: function() {
Undo.initEdit({elements: Cube.selected})
@@ -717,14 +720,13 @@ BARS.defineActions(function() {
Undo.finishEdit('resize')
}
})
- new NumSlider({
- id: 'slider_size_z',
+ new NumSlider('slider_size_z', {
condition: () => (Cube.selected.length && Modes.edit),
get: function() {
return Cube.selected[0].to[2] - Cube.selected[0].from[2]
},
change: function(value, fixed) {
- scaleOnAxis(value, fixed, 2)
+ resizeOnAxis(value, fixed, 2)
},
onBefore: function() {
Undo.initEdit({elements: Cube.selected})
@@ -734,19 +736,24 @@ BARS.defineActions(function() {
}
})
//Inflate
- new NumSlider({
- id: 'slider_inflate',
+ new NumSlider('slider_inflate', {
condition: function() {return Cube.selected.length && Modes.edit},
get: function() {
return Cube.selected[0].inflate
},
change: function(value, fixed) {
Cube.selected.forEach(function(obj, i) {
- var diff = value
- if (fixed) {
- diff -= obj.inflate
+ var v = value
+ if (!fixed) {
+ v += obj.inflate
}
- obj.inflate = obj.inflate + diff
+ v = obj.from[0] - Math.clamp(obj.from[0]-v, -16, 32);
+ v = obj.from[1] - Math.clamp(obj.from[1]-v, -16, 32);
+ v = obj.from[2] - Math.clamp(obj.from[2]-v, -16, 32);
+ v = Math.clamp(obj.to[0]+v, -16, 32) - obj.to[0];
+ v = Math.clamp(obj.to[1]+v, -16, 32) - obj.to[1];
+ v = Math.clamp(obj.to[2]+v, -16, 32) - obj.to[2];
+ obj.inflate = v
})
Canvas.updatePositions()
},
@@ -759,8 +766,7 @@ BARS.defineActions(function() {
})
//Rotation
- new NumSlider({
- id: 'slider_rotation_x',
+ new NumSlider('slider_rotation_x', {
condition: () => (Modes.edit && getRotationObject()),
get: function() {
if (Format.bone_rig && Group.selected) {
@@ -782,8 +788,7 @@ BARS.defineActions(function() {
},
getInterval: getRotationInterval
})
- new NumSlider({
- id: 'slider_rotation_y',
+ new NumSlider('slider_rotation_y', {
condition: () => (Modes.edit && getRotationObject()),
get: function() {
if (Format.bone_rig && Group.selected) {
@@ -805,8 +810,7 @@ BARS.defineActions(function() {
},
getInterval: getRotationInterval
})
- new NumSlider({
- id: 'slider_rotation_z',
+ new NumSlider('slider_rotation_z', {
condition: () => (Modes.edit && getRotationObject()),
get: function() {
if (Format.bone_rig && Group.selected) {
@@ -858,8 +862,7 @@ BARS.defineActions(function() {
})
Canvas.updatePositions()
}
- new NumSlider({
- id: 'slider_origin_x',
+ new NumSlider('slider_origin_x', {
condition: () => (Modes.edit && getRotationObject()),
get: function() {
if (Format.bone_rig && Group.selected) {
@@ -879,8 +882,7 @@ BARS.defineActions(function() {
Undo.finishEdit('origin')
}
})
- new NumSlider({
- id: 'slider_origin_y',
+ new NumSlider('slider_origin_y', {
condition: () => (Modes.edit && getRotationObject()),
get: function() {
if (Format.bone_rig && Group.selected) {
@@ -900,8 +902,7 @@ BARS.defineActions(function() {
Undo.finishEdit('origin')
}
})
- new NumSlider({
- id: 'slider_origin_z',
+ new NumSlider('slider_origin_z', {
condition: () => (Modes.edit && getRotationObject()),
get: function() {
if (Format.bone_rig && Group.selected) {
@@ -922,8 +923,7 @@ BARS.defineActions(function() {
}
})
- new Action({
- id: 'scale',
+ new Action('scale', {
icon: 'settings_overscan',
category: 'transform',
click: function () {
@@ -953,8 +953,7 @@ BARS.defineActions(function() {
scaleAll(false, 1)
}
})
- new Action({
- id: 'rotate_x_cw',
+ new Action('rotate_x_cw', {
icon: 'rotate_right',
color: 'x',
category: 'transform',
@@ -962,8 +961,7 @@ BARS.defineActions(function() {
rotateSelected(0, 1);
}
})
- new Action({
- id: 'rotate_x_ccw',
+ new Action('rotate_x_ccw', {
icon: 'rotate_left',
color: 'x',
category: 'transform',
@@ -971,8 +969,7 @@ BARS.defineActions(function() {
rotateSelected(0, 3);
}
})
- new Action({
- id: 'rotate_y_cw',
+ new Action('rotate_y_cw', {
icon: 'rotate_right',
color: 'y',
category: 'transform',
@@ -980,8 +977,7 @@ BARS.defineActions(function() {
rotateSelected(1, 1);
}
})
- new Action({
- id: 'rotate_y_ccw',
+ new Action('rotate_y_ccw', {
icon: 'rotate_left',
color: 'y',
category: 'transform',
@@ -989,8 +985,7 @@ BARS.defineActions(function() {
rotateSelected(1, 3);
}
})
- new Action({
- id: 'rotate_z_cw',
+ new Action('rotate_z_cw', {
icon: 'rotate_right',
color: 'z',
category: 'transform',
@@ -998,8 +993,7 @@ BARS.defineActions(function() {
rotateSelected(2, 1);
}
})
- new Action({
- id: 'rotate_z_ccw',
+ new Action('rotate_z_ccw', {
icon: 'rotate_left',
color: 'z',
category: 'transform',
@@ -1008,17 +1002,15 @@ BARS.defineActions(function() {
}
})
- new Action({
- id: 'flip_x',
+ new Action('flip_x', {
icon: 'icon-mirror_x',
color: 'x',
category: 'transform',
click: function () {
- mirrorSelected(0);
+ mirrorSelected(0);
}
})
- new Action({
- id: 'flip_y',
+ new Action('flip_y', {
icon: 'icon-mirror_y',
color: 'y',
category: 'transform',
@@ -1026,8 +1018,7 @@ BARS.defineActions(function() {
mirrorSelected(1);
}
})
- new Action({
- id: 'flip_z',
+ new Action('flip_z', {
icon: 'icon-mirror_z',
color: 'z',
category: 'transform',
@@ -1036,8 +1027,7 @@ BARS.defineActions(function() {
}
})
- new Action({
- id: 'center_x',
+ new Action('center_x', {
icon: 'vertical_align_center',
color: 'x',
category: 'transform',
@@ -1047,8 +1037,7 @@ BARS.defineActions(function() {
Undo.finishEdit('center')
}
})
- new Action({
- id: 'center_y',
+ new Action('center_y', {
icon: 'vertical_align_center',
color: 'y',
category: 'transform',
@@ -1058,8 +1047,7 @@ BARS.defineActions(function() {
Undo.finishEdit('center')
}
})
- new Action({
- id: 'center_z',
+ new Action('center_z', {
icon: 'vertical_align_center',
color: 'z',
category: 'transform',
@@ -1069,8 +1057,7 @@ BARS.defineActions(function() {
Undo.finishEdit('center')
}
})
- new Action({
- id: 'center_all',
+ new Action('center_all', {
icon: 'filter_center_focus',
category: 'transform',
click: function () {
@@ -1080,40 +1067,34 @@ BARS.defineActions(function() {
}
})
- new Action({
- id: 'toggle_visibility',
+ new Action('toggle_visibility', {
icon: 'visibility',
category: 'transform',
click: function () {toggleCubeProperty('visibility')}
})
- new Action({
- id: 'toggle_export',
+ new Action('toggle_export', {
icon: 'save',
category: 'transform',
click: function () {toggleCubeProperty('export')}
})
- new Action({
- id: 'toggle_autouv',
+ new Action('toggle_autouv', {
icon: 'fullscreen_exit',
category: 'transform',
click: function () {toggleCubeProperty('autouv')}
})
- new Action({
- id: 'toggle_shade',
+ new Action('toggle_shade', {
icon: 'wb_sunny',
category: 'transform',
condition: () => !Project.box_uv,
click: function () {toggleCubeProperty('shade')}
})
- new Action({
- id: 'toggle_mirror_uv',
+ new Action('toggle_mirror_uv', {
icon: 'icon-mirror_x',
category: 'transform',
condition: () => Project.box_uv,
click: function () {toggleCubeProperty('shade')}
})
- new Action({
- id: 'update_autouv',
+ new Action('update_autouv', {
icon: 'brightness_auto',
category: 'transform',
condition: () => !Project.box_uv,
@@ -1127,14 +1108,12 @@ BARS.defineActions(function() {
}
}
})
- new Action({
- id: 'origin_to_geometry',
+ new Action('origin_to_geometry', {
icon: 'filter_center_focus',
category: 'transform',
click: function () {origin2geometry()}
})
- new Action({
- id: 'rescale_toggle',
+ new Action('rescale_toggle', {
icon: 'check_box_outline_blank',
category: 'transform',
condition: function() {return Format.rotation_limit && Cube.selected.length;},
@@ -1149,8 +1128,7 @@ BARS.defineActions(function() {
Undo.finishEdit('rescale')
}
})
- new Action({
- id: 'bone_reset_toggle',
+ new Action('bone_reset_toggle', {
icon: 'check_box_outline_blank',
category: 'transform',
condition: function() {return Format.bone_rig && Group.selected;},
@@ -1162,8 +1140,7 @@ BARS.defineActions(function() {
}
})
- new Action({
- id: 'remove_blank_faces',
+ new Action('remove_blank_faces', {
icon: 'cancel_presentation',
category: 'filter',
condition: () => !Format.box_uv,
diff --git a/js/undo.js b/js/undo.js
index 2deca9b7c..a6c53253b 100644
--- a/js/undo.js
+++ b/js/undo.js
@@ -1,7 +1,7 @@
var Undo = {
index: 0,
history: [],
- initEdit: function(aspects) {
+ initEdit(aspects) {
if (aspects && aspects.cubes) {
console.warn('Aspect "cubes" is deprecated. Please use "elements" instead.');
aspects.elements = aspects.cubes;
@@ -23,7 +23,7 @@ var Undo = {
Undo.current_save = new Undo.save(aspects)
return Undo.current_save;
},
- finishEdit: function(action, aspects) {
+ finishEdit(action, aspects) {
if (aspects && aspects.cubes) {
console.warn('Aspect "cubes" is deprecated. Please use "elements" instead.');
aspects.elements = aspects.cubes;
@@ -58,13 +58,25 @@ var Undo = {
}
return entry;
},
- cancelEdit: function() {
+ cancelEdit() {
if (!Undo.current_save) return;
outlines.children.length = 0
Undo.loadSave(Undo.current_save, new Undo.save(Undo.current_save.aspects))
delete Undo.current_save;
},
- undo: function(remote) {
+ addKeyframeCasualties(arr) {
+ if (!arr || arr.length == 0) return;
+ if (!Undo.current_save.keyframes) {
+ Undo.current_save.keyframes = {
+ animation: Animator.selected.uuid
+ }
+ }
+ arr.forEach(kf => {
+ Undo.current_save.affected = true
+ Undo.current_save.keyframes[kf.uuid] = kf.getUndoCopy();
+ })
+ },
+ undo(remote) {
if (Undo.history.length <= 0 || Undo.index < 1) return;
Prop.project_saved = false;
@@ -77,7 +89,7 @@ var Undo = {
}
Blockbench.dispatchEvent('undo', {entry})
},
- redo: function(remote) {
+ redo(remote) {
if (Undo.history.length <= 0) return;
if (Undo.index >= Undo.history.length) {
return;
@@ -92,7 +104,7 @@ var Undo = {
}
Blockbench.dispatchEvent('redo', {entry})
},
- remoteEdit: function(entry) {
+ remoteEdit(entry) {
Undo.loadSave(entry.post, entry.before, 'session')
if (entry.save_history !== false) {
@@ -106,7 +118,7 @@ var Undo = {
Blockbench.dispatchEvent('finished_edit', {remote: true})
}
},
- getItemByUUID: function(list, uuid) {
+ getItemByUUID(list, uuid) {
if (!list || typeof list !== 'object' || !list.length) {return false;}
var i = 0;
while (i < list.length) {
@@ -173,10 +185,9 @@ var Undo = {
scope.animations[a.uuid] = a.getUndoCopy();
})
}
- if (aspects.keyframes && Animator.selected && Animator.selected.getBoneAnimator()) {
+ if (aspects.keyframes && Animator.selected && Timeline.animators.length) {
this.keyframes = {
- animation: Animator.selected.uuid,
- bone: Animator.selected.getBoneAnimator().uuid
+ animation: Animator.selected.uuid
}
aspects.keyframes.forEach(kf => {
scope.keyframes[kf.uuid] = kf.getUndoCopy()
@@ -194,7 +205,7 @@ var Undo = {
})
}
},
- loadSave: function(save, reference, mode) {
+ loadSave(save, reference, mode) {
var is_session = mode === 'session';
if (save.elements) {
for (var uuid in save.elements) {
@@ -327,7 +338,7 @@ var Undo = {
if (save.animations) {
for (var uuid in save.animations) {
- var animation = reference.animations[uuid] ? Undo.getItemByUUID(Animator.animations, uuid) : null;
+ var animation = (reference.animations && reference.animations[uuid]) ? Undo.getItemByUUID(Animator.animations, uuid) : null;
if (!animation) {
animation = new Animation()
animation.uuid = uuid
@@ -356,57 +367,47 @@ var Undo = {
}
}
if (animation) {
- var bone = Animator.selected.getBoneAnimator();
- if (!bone || bone.uuid !== save.keyframes.bone) {
- for (var uuid in Animator.selected.bones) {
- if (uuid === save.keyframes.bone) {
- bone = Animator.selected.bones[uuid]
- if (bone.group && Animator.open && !is_session) {
- bone.group.select()
- }
- }
- }
- }
- if (bone.uuid === save.keyframes.bone) {
- function getKeyframe(uuid) {
- var i = 0;
- while (i < bone.keyframes.length) {
- if (bone.keyframes[i].uuid === uuid) {
- return bone.keyframes[i];
- }
- i++;
+ function getKeyframe(uuid, animator) {
+ var i = 0;
+ while (i < animator.keyframes.length) {
+ if (animator.keyframes[i].uuid === uuid) {
+ return animator.keyframes[i];
}
+ i++;
}
- var added = 0;
- for (var uuid in save.keyframes) {
- if (uuid.length === 36 && save.keyframes.hasOwnProperty(uuid)) {
- var data = save.keyframes[uuid]
- var kf = getKeyframe(uuid)
- if (kf) {
- kf.extend(data)
- } else {
- kf = new Keyframe(data, uuid)
- kf.parent = bone;
- bone.keyframes.push(kf)
- added++;
- }
+ }
+ var added = 0;
+ for (var uuid in save.keyframes) {
+ if (uuid.length === 36 && save.keyframes.hasOwnProperty(uuid)) {
+ var data = save.keyframes[uuid];
+ var animator = animation.animators[data.animator];
+ if (!animator) continue;
+ var kf = getKeyframe(uuid, animator);
+ if (kf) {
+ kf.extend(data)
+ } else {
+ animator.addKeyframe(data, uuid);
+ added++;
}
}
- for (var uuid in reference.keyframes) {
- if (uuid.length === 36 && reference.keyframes.hasOwnProperty(uuid) && !save.keyframes.hasOwnProperty(uuid)) {
- var kf = getKeyframe(uuid)
- if (kf) {
- kf.remove()
- }
+ }
+ for (var uuid in reference.keyframes) {
+ if (uuid.length === 36 && reference.keyframes.hasOwnProperty(uuid) && !save.keyframes.hasOwnProperty(uuid)) {
+ var data = reference.keyframes[uuid];
+ var animator = animation.animators[data.animator];
+ if (!animator) continue;
+ var kf = getKeyframe(uuid, animator)
+ if (kf) {
+ kf.remove()
}
}
- if (added) {
- Vue.nextTick(Timeline.update)
- }
- updateKeyframeSelection()
- Animator.preview()
}
+ if (added) {
+ Vue.nextTick(Timeline.update)
+ }
+ updateKeyframeSelection()
+ Animator.preview()
}
}
@@ -440,8 +441,7 @@ Undo.save.prototype.addTexture = function(texture) {
}
BARS.defineActions(function() {
- new Action({
- id: 'undo',
+ new Action('undo', {
icon: 'undo',
category: 'edit',
condition: () => (!open_dialog || open_dialog === 'uv_dialog' || open_dialog === 'toolbar_edit'),
@@ -449,8 +449,7 @@ BARS.defineActions(function() {
keybind: new Keybind({key: 90, ctrl: true}),
click: Undo.undo
})
- new Action({
- id: 'redo',
+ new Action('redo', {
icon: 'redo',
category: 'edit',
condition: () => (!open_dialog || open_dialog === 'uv_dialog' || open_dialog === 'toolbar_edit'),
diff --git a/js/util.js b/js/util.js
index 10e4f09d8..39f0a6fcb 100644
--- a/js/util.js
+++ b/js/util.js
@@ -55,6 +55,16 @@ Date.prototype.getTimestamp = function() {
var l2 = i => (i.toString().length === 1 ? '0'+i : i);
return l2(this.getHours()) + ':' + l2(this.getMinutes());
}
+Object.defineProperty(Event.prototype, 'ctrlOrCmd', {
+ get: function() {
+ return this.ctrlKey || this.metaKey;
+ }
+})
+Object.defineProperty($.Event.prototype, 'ctrlOrCmd', {
+ get: function() {
+ return this.ctrlKey || this.metaKey;
+ }
+})
//Jquery
$.fn.deepest = function() {
diff --git a/js/uv.js b/js/uv.js
index 141e6b718..fd6deb98c 100644
--- a/js/uv.js
+++ b/js/uv.js
@@ -283,6 +283,7 @@ class UVEditor {
scope.save()
scope.displaySliders()
+ return true;
},
stop: function(event, ui) {
scope.save()
@@ -318,7 +319,7 @@ class UVEditor {
})
this.jquery.frame.click(function(event) {
- if (!dragging_not_clicking && event.ctrlKey) {
+ if (!dragging_not_clicking && event.ctrlOrCmd) {
scope.reverseSelect(event)
}
dragging_not_clicking = false;
@@ -334,7 +335,7 @@ class UVEditor {
}
})
this.jquery.viewport.on('mousewheel', function(e) {
- if (e.ctrlKey) {
+ if (e.ctrlOrCmd) {
var n = (event.deltaY < 0) ? 0.1 : -0.1;
n *= scope.zoom
var number = limitNumber(scope.zoom + n, 1.0, 4.0)
@@ -540,7 +541,7 @@ class UVEditor {
grid = Project.texture_width
} else if (grid === undefined || grid === 'dialog') {
this.autoGrid = false;
- grid = BarItems.uv_grid.get()
+ grid = BarItems.uv_grid.get()||'auto';
if (grid === 'auto') {
if (this.texture) {
grid = this.texture.width
@@ -579,7 +580,7 @@ class UVEditor {
$('.panel#uv').append(this.jquery.main)
this.jquery.main.on('mousewheel', function(e) {
- if (!Project.box_uv && !e.ctrlKey) {
+ if (!Project.box_uv && !e.ctrlOrCmd) {
var faceIDs = {'north': 0, 'south': 1, 'west': 2, 'east': 3, 'up': 4, 'down': 5}
var id = faceIDs[scope.face]
event.deltaY > 0 ? id++ : id--;
@@ -625,7 +626,7 @@ class UVEditor {
if (!Project.box_uv) {
main_uv.setFace(face_match);
}
- //if (!event.ctrlKey && !event.shiftKey) {
+ //if (!event.ctrlOrCmd && !event.shiftKey) {
//}
selected.empty();
matches.forEach(s => {
@@ -742,7 +743,7 @@ class UVEditor {
this.jquery.frame.css('background-size', 'contain')
}
this.texture = tex;
- if (this.autoGrid) {
+ if (this.autoGrid && Format.single_texture) {
this.setGrid(Project.texture_width, false)
} else {
this.setGrid(undefined, false)
@@ -1438,7 +1439,7 @@ class UVEditor {
Undo.initEdit({elements: Cube.selected})
Cube.selected.forEach((obj) => {
editor.getFaces(event).forEach(function(side) {
- delete obj.faces[side].texture;
+ obj.faces[side].texture = false;
})
Canvas.adaptObjectFaces(obj)
})
@@ -1617,7 +1618,7 @@ const uv_dialog = {
uv_dialog.selection = uv_dialog.selection_all.splice(0, 10)
uv_dialog.updateSelection()
- BarItems.uv_grid.set(uv_dialog.editors.north.gridSelectOption)
+ //BarItems.uv_grid.set(uv_dialog.editors.north.gridSelectOption)
$('.dialog#uv_dialog').width(uv_dialog.all_size.x)
$('.dialog#uv_dialog').height(uv_dialog.all_size.y)
@@ -1628,7 +1629,7 @@ const uv_dialog = {
uv_dialog.editors.single.setFace(tab)
uv_dialog.selection_all = uv_dialog.selection.splice(0, 10)
uv_dialog.selection = [tab]
- BarItems.uv_grid.set(uv_dialog.editors.single.gridSelectOption)
+ //BarItems.uv_grid.set(uv_dialog.editors.single.gridSelectOption)
var max_size = $(window).height() - 200
if (max_size < uv_dialog.editors.single.size ) {
@@ -1656,10 +1657,10 @@ const uv_dialog = {
x: obj.width(),
y: obj.height()
}
+ var menu_gap = 98 + ($('#uv_tab_bar').is(':visible') ? 30 : 0) + ($('.toolbar_wrapper.uv_dialog').height()||0);
if (uv_dialog.single) {
- var menu_gap = Project.box_uv ? 66 : 154
var editor_size = size.x-16
- size.y = (size.y - menu_gap) * (Project.box_uv ? Project.texture_width/Project.texture_height : 1)
+ size.y = (size.y - menu_gap) * (Project.texture_width/Project.texture_height)
if (size.x > size.y) {
editor_size = size.y
}
@@ -1667,13 +1668,13 @@ const uv_dialog = {
} else {
var centerUp = false
+ size.y -= menu_gap;
if (size.x < size.y/1.2) {
var editor_size = limitNumber(size.x / 2 - 35, 80, $(window).height()/3-120)
- editor_size = limitNumber(editor_size, 80, (size.y-64)/3-120)
+ editor_size = limitNumber(editor_size, 80, (size.y-64)/3 - 50)
} else {
//4 x 2
- var y_margin = 130
- var editor_size = limitNumber(size.x/4-25, 16, size.y/2-y_margin)
+ var editor_size = limitNumber(size.x/4-25, 16, size.y/2 - 60)
centerUp = true
}
editor_size = editor_size - (editor_size % 16)
@@ -1776,22 +1777,19 @@ const uv_dialog = {
}
BARS.defineActions(function() {
- new Action({
- id: 'uv_dialog',
+ new Action('uv_dialog', {
icon: 'view_module',
category: 'blockbench',
condition: () => !Project.box_uv && Cube.selected.length,
click: function () {uv_dialog.openAll()}
})
- new Action({
- id: 'uv_dialog_full',
+ new Action('uv_dialog_full', {
icon: 'web_asset',
category: 'blockbench',
click: function () {uv_dialog.openFull()}
})
- new BarSlider({
- id: 'uv_rotation',
+ new BarSlider('uv_rotation', {
category: 'uv',
condition: () => !Project.box_uv && Format.id == 'java_block' && Cube.selected.length,
min: 0, max: 270, step: 90, width: 80,
@@ -1805,8 +1803,7 @@ BARS.defineActions(function() {
Undo.finishEdit('uv rotate')
}
})
- new BarSelect({
- id: 'uv_grid',
+ new BarSelect('uv_grid', {
category: 'uv',
condition: () => !Project.box_uv && Cube.selected.length,
width: 60,
@@ -1829,16 +1826,14 @@ BARS.defineActions(function() {
}
})
- new Action({
- id: 'uv_select_all',
+ new Action('uv_select_all', {
icon: 'view_module',
category: 'uv',
condition: () => open_dialog === 'uv_dialog',
click: uv_dialog.selectAll
})
- new Action({
- id: 'uv_maximize',
+ new Action('uv_maximize', {
icon: 'zoom_out_map',
category: 'uv',
condition: () => !Project.box_uv && Cube.selected.length,
@@ -1848,8 +1843,7 @@ BARS.defineActions(function() {
Undo.finishEdit('uv maximize')
}
})
- new Action({
- id: 'uv_turn_mapping',
+ new Action('uv_turn_mapping', {
icon: 'screen_rotation',
category: 'uv',
condition: () => !Project.box_uv && Cube.selected.length,
@@ -1859,8 +1853,7 @@ BARS.defineActions(function() {
Undo.finishEdit('turn uv mapping')
}
})
- new Action({
- id: 'uv_auto',
+ new Action('uv_auto', {
icon: 'brightness_auto',
category: 'uv',
condition: () => !Project.box_uv && Cube.selected.length,
@@ -1870,8 +1863,7 @@ BARS.defineActions(function() {
Undo.finishEdit('auto uv')
}
})
- new Action({
- id: 'uv_rel_auto',
+ new Action('uv_rel_auto', {
icon: 'brightness_auto',
category: 'uv',
condition: () => !Project.box_uv && Cube.selected.length,
@@ -1881,8 +1873,7 @@ BARS.defineActions(function() {
Undo.finishEdit('auto uv')
}
})
- new Action({
- id: 'uv_mirror_x',
+ new Action('uv_mirror_x', {
icon: 'icon-mirror_x',
category: 'uv',
condition: () => !Project.box_uv && Cube.selected.length,
@@ -1892,8 +1883,7 @@ BARS.defineActions(function() {
Undo.finishEdit('mirror uv')
}
})
- new Action({
- id: 'uv_mirror_y',
+ new Action('uv_mirror_y', {
icon: 'icon-mirror_y',
category: 'uv',
condition: () => !Project.box_uv && Cube.selected.length,
@@ -1903,8 +1893,7 @@ BARS.defineActions(function() {
Undo.finishEdit('mirror uv')
}
})
- new Action({
- id: 'uv_transparent',
+ new Action('uv_transparent', {
icon: 'clear',
category: 'uv',
condition: () => !Project.box_uv && Cube.selected.length,
@@ -1912,8 +1901,7 @@ BARS.defineActions(function() {
uv_dialog.forSelection('clear', event)
}
})
- new Action({
- id: 'uv_reset',
+ new Action('uv_reset', {
icon: 'replay',
category: 'uv',
condition: () => !Project.box_uv && Cube.selected.length,
@@ -1923,8 +1911,7 @@ BARS.defineActions(function() {
Undo.finishEdit('reset uv')
}
})
- new Action({
- id: 'uv_apply_all',
+ new Action('uv_apply_all', {
icon: 'format_color_fill',
category: 'uv',
condition: () => !Project.box_uv && Cube.selected.length,
@@ -1934,8 +1921,7 @@ BARS.defineActions(function() {
Undo.finishEdit('uv apply all')
}
})
- new BarSelect({
- id: 'cullface',
+ new BarSelect('cullface', {
category: 'uv',
condition: () => !Project.box_uv && Cube.selected.length,
options: {
@@ -1953,8 +1939,7 @@ BARS.defineActions(function() {
Undo.finishEdit('cullface')
}
})
- new Action({
- id: 'auto_cullface',
+ new Action('auto_cullface', {
icon: 'block',
category: 'uv',
condition: () => !Project.box_uv && Cube.selected.length,
@@ -1964,8 +1949,7 @@ BARS.defineActions(function() {
Undo.finishEdit('auto cullface')
}
})
- new Action({
- id: 'face_tint',
+ new Action('face_tint', {
category: 'uv',
condition: () => !Project.box_uv && Cube.selected.length,
click: function (event) {
@@ -1974,8 +1958,7 @@ BARS.defineActions(function() {
Undo.finishEdit('tint')
}
})
- new Action({
- id: 'uv_shift',
+ new Action('uv_shift', {
condition: () => Project.box_uv,
icon: 'photo_size_select_large',
category: 'uv',
@@ -1983,8 +1966,7 @@ BARS.defineActions(function() {
showUVShiftDialog()
}
})
- new Action({
- id: 'toggle_uv_overlay',
+ new Action('toggle_uv_overlay', {
condition: () => Project.box_uv,
icon: 'crop_landscape',//'crop_landscape'
category: 'uv',
diff --git a/js/web.js b/js/web.js
index 5a4f7e502..aa3b31429 100644
--- a/js/web.js
+++ b/js/web.js
@@ -55,8 +55,7 @@ function showSaveDialog(close) {
BARS.defineActions(function() {
if (Blockbench.isMobile) {
- new Action({
- id: 'sidebar_left',
+ new Action('sidebar_left', {
icon: 'burst_mode',
category: 'view',
condition: () => !Modes.start,
@@ -74,8 +73,7 @@ BARS.defineActions(function() {
resizeWindow()
}
})
- new Action({
- id: 'sidebar_right',
+ new Action('sidebar_right', {
icon: 'view_list',
category: 'view',
condition: () => !Modes.start,
diff --git a/lang/de.json b/lang/de.json
index 0ad4c06de..e58b8e985 100644
--- a/lang/de.json
+++ b/lang/de.json
@@ -77,7 +77,7 @@
"message.bedrock_overwrite_error.overwrite": "Überschreiben",
"message.close_warning.message": "Möchtest du dein Modell speichern?",
"message.close_warning.web": "Bist du sicher, dass du das Program verlassen möchtest? Dadurch geht die momentane Arbeit verloren.",
- "message.default_textures.title": "Standardttexturen",
+ "message.default_textures.title": "Standardtexturen",
"message.default_textures.message": "Wähle den \"textures\" Ordner deines Ressourcenpakets",
"message.default_textures.detail": "Extrahiere das Standard-Ressourcenpaket aus der Minecraft .jar oder lade es aus dem Internet herunder. Finde dann den \"textures\" Ordner und öffne diesen. Blockbench merkt sich diesen Ordner und greift in Zukunft darauf zurück, wenn es die Texturen nicht anderweitig finden kann.",
"message.default_textures.select": "Ordner auswählen",
@@ -106,7 +106,6 @@
"dialog.project.width": "Textur-Breite",
"dialog.project.height": "Textur-Höhe",
"dialog.texture.title": "Textur",
- "dialog.texture.name": "Name",
"dialog.texture.variable": "Alias",
"dialog.texture.namespace": "Namensraum",
"dialog.texture.folder": "Ordner",
@@ -150,9 +149,8 @@
"dialog.entitylist.bones": "Knochen",
"dialog.entitylist.cubes": "Elemente",
"dialog.create_texture.title": "Textur erstellen",
- "dialog.create_texture.name": "Name",
"dialog.create_texture.folder": "Ordner",
- "dialog.create_texture.template": "Vorlage",
+ "dialog.create_texture.template": "Template",
"dialog.create_texture.resolution": "Auflösung",
"dialog.input.title": "Eingabe",
"dialog.update.title": "Aktualisierungen",
@@ -163,7 +161,7 @@
"dialog.settings.keybinds": "Tastenbelegungen",
"dialog.settings.layout": "Layout",
"dialog.settings.about": "Über",
- "layout.color.back": "Zurück",
+ "layout.color.back": "Dunkler",
"layout.color.back.desc": "Hintergründe und Eingabefelder",
"layout.color.dark": "Dunkel",
"layout.color.dark.desc": "Huntergrund der Vorschau",
@@ -193,7 +191,7 @@
"about.creator": "Entwickler: ",
"about.website": "Webseite: ",
"about.bugtracker": "Fehlerdatenbank",
- "about.electron": "Diese App wurde mit Electron erstellt. Dass ist ein Framework zur Erstellungen von Nativen Programmen mit Web-Technologien wie Javascript, HTML und CSS.",
+ "about.electron": "Diese App wurde mit Electron erstellt. Electron ist ein Framework zur Erstellungen von Nativen Programmen mit Web-Technologien wie Javascript, HTML und CSS.",
"about.vertex_snap": "Der Eckpunktmagnet basiert auf einem Plugin von SirBenet",
"about.icons": "Symbolpakete: ",
"about.libraries": "Bibliotheken: ",
@@ -478,7 +476,7 @@
"action.import_texture": "Textur importieren",
"action.import_texture.desc": "Eine oder mehrere Texturen von deinem Computer öffnen",
"action.create_texture": "Textur erstellen",
- "action.create_texture.desc": "Erstellt eine leere Textur oder Texturvorlage",
+ "action.create_texture.desc": "Erstellt eine leere Textur oder Texturtemplate",
"action.save_textures": "Texturen speichern",
"action.save_textures.desc": "Speichert alle ungespeicherten Texturen",
"action.animated_textures": "Animierte Texturen",
@@ -591,7 +589,6 @@
"display.scale": "Größe",
"display.slot": "Position",
"display.reference": "Referenzmodell",
- "display.presetname": "Name",
"display.reference.player": "Spieler",
"display.reference.zombie": "Zombie",
"display.reference.armor_stand": "Rüstungsständer",
@@ -791,9 +788,7 @@
"action.open_backup_folder": "Backup-Ordner öffnen",
"action.open_backup_folder.desc": "Öffnet den Backup-Ordner von Blockbench",
"switches.mirror": "UV Spiegeln",
- "Name of the Language you are editing, NOT English": {
- "language_name": "Deutsch"
- },
+ "language_name": "Deutsch",
"message.plugin_reload": "%0 lokale Plugins wurden neugeladen",
"settings.brightness": "Helligkeit",
"settings.brightness.desc": "Helligkeit der Vorschau. Standardwert ist 50",
@@ -912,7 +907,6 @@
"message.wireframe.disabled": "Drahtgitteransicht deaktiviert",
"dialog.project.box_uv": "Box UV",
"dialog.convert_project.title": "Projekt konvertieren",
- "dialog.convert_project.format": "Format",
"dialog.convert_project.text": "Bist du dir sicher, dass du das Projekt konvertieren möchtest? Die Änderung kann nicht rückgängig gemacht werden.",
"dialog.create_texture.double_use": "Mehrfachbelegung beibehalten",
"dialog.model_stats.title": "Modellstatistiken",
@@ -966,5 +960,28 @@
"panel.element.origin": "Angelpunkt",
"panel.element.rotation": "Rotation",
"message.canvas_limit_error.title": "Begrenzungsfehler",
- "message.canvas_limit_error.message": "Die Aktion konnte nicht korrekt durchgeführt werden, weil das Format das Modell auf einen Bereich von 48 Einheiten beschränkt. Verschiebe den Angelpunkt, um das zu verhindern."
+ "message.canvas_limit_error.message": "Die Aktion konnte nicht korrekt durchgeführt werden, weil das Format das Modell auf einen Bereich von 48 Einheiten beschränkt. Verschiebe den Angelpunkt, um das zu verhindern.",
+ "data.effect": "Effekt",
+ "generic.name": "Name",
+ "settings.recent_projects": "Zuletzt verwendet Maximum",
+ "settings.recent_projects.desc": "Maximale Anzahl an zuletzt verwendeten Modellen, die angezeigt werden",
+ "settings.volume": "Lautstärke",
+ "settings.volume.desc": "Lautstärke for Soundeffekte in Animationen",
+ "action.change_keyframe_file": "Datei auswählen",
+ "action.change_keyframe_file.desc": "Eine Audiodatei auswählen, um einen Soundeffekt vorzuhören",
+ "action.clear_timeline": "Timeline aufräumen",
+ "action.clear_timeline.desc": "Räumt alle nicht ausgewählten Animatoren aus der Timeline",
+ "action.select_effect_animator": "Effekte bearbeiten",
+ "action.select_effect_animator.desc": "Öffnet eine Timeline zur Bearbeitung von Sounds und Partikeln",
+ "action.timeline_focus": "Kanal",
+ "action.timeline_focus.desc": "Wähle den Animationskanal, der in der Timeline angezeigt wird",
+ "action.timeline_focus.all": "Alle",
+ "timeline.particle": "Partikel",
+ "timeline.sound": "Sound",
+ "timeline.effects": "Effekte",
+ "data.format": "Format",
+ "format.optifine_part": "OptiFine Part",
+ "format.optifine_part.desc": "JPM Part für OptiFine-Entitymodelle",
+ "action.reverse_keyframes": "Keyframes umkehren",
+ "action.reverse_keyframes.desc": "Kehrt die Reihenfolge der ausgewählten Keyframes um"
}
\ No newline at end of file
diff --git a/lang/en.json b/lang/en.json
index 8d46d75ee..e902e66b9 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -17,18 +17,21 @@
"data.locator": "Locator",
"data.group": "Group",
"data.texture": "Texture",
- "data.origin": "Origin",
+ "data.origin": "Pivot",
"data.plugin": "Plugin",
"data.preview": "Preview",
"data.toolbar": "Toolbar",
"data.separator": "Separator",
"data.image": "Image",
+ "data.format": "Format",
+ "data.effect": "Effect",
"generic.delete": "Delete",
"generic.remove": "Remove",
"generic.rename": "Rename",
"generic.download": "Download",
"generic.search": "Search",
+ "generic.name": "Name",
"dates.today": "Today",
"dates.yesterday": "Yesterday",
@@ -57,6 +60,8 @@
"format.modded_entity.desc": "Entity model for mods. Can be exported as .java class files.",
"format.optifine_entity": "OptiFine Entity",
"format.optifine_entity.desc": "Custom entity model for OptiFine",
+ "format.optifine_part": "OptiFine Part",
+ "format.optifine_part.desc": "JPM part for OptiFine entity models",
"keys.mouse": "Mouse Button %0",
"keys.ctrl": "Control",
@@ -99,7 +104,7 @@
"status_bar.processing_gif":"Processing GIF",
"message.canvas_limit_error.title": "Canvas Limit Error",
- "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the origin to prevent this.",
+ "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the pivot point to prevent this.",
"message.rotation_limit.title": "Rotation Limits",
"message.rotation_limit.message": "Rotations are limited by Minecraft to one axis and 22.5 degree increments. Rotating on a different axis will clear all rotations on the other axes. Convert the model to \"Free Model\" if you are modeling for other purposes and need free rotations.",
"message.file_not_found.title": "File Not Found",
@@ -210,11 +215,9 @@
"dialog.project.height": "Texture Height",
"dialog.convert_project.title": "Convert Project",
- "dialog.convert_project.format": "Format",
"dialog.convert_project.text": "Are you sure you want to convert this project? You cannot undo this step.",
"dialog.texture.title": "Texture",
- "dialog.texture.name": "Name",
"dialog.texture.variable": "Variable",
"dialog.texture.namespace": "Namespace",
"dialog.texture.folder": "Folder",
@@ -269,7 +272,6 @@
"dialog.entitylist.cubes": "Cubes",
"dialog.create_texture.title": "Create Texture",
- "dialog.create_texture.name": "Name",
"dialog.create_texture.folder": "Folder",
"dialog.create_texture.template": "Template",
"dialog.create_texture.compress": "Compress Template",
@@ -381,13 +383,15 @@
"settings.username.desc": "Username for edit sessions",
"settings.show_actions": "Display Actions",
"settings.show_actions.desc": "Display every action in the status bar",
+ "settings.recent_projects": "Recent Model Cap",
+ "settings.recent_projects.desc": "Maximum number of recent models to remember",
"settings.backup_interval": "Backup Interval",
"settings.backup_interval.desc": "Interval of the automatic backups in minutes",
"settings.backup_retain": "Backup Retain Duration",
"settings.backup_retain.desc": "Set how long Blockbench retains old backups in days",
- "settings.origin_size": "Rotation Origin",
- "settings.origin_size.desc": "Size of the rotation origin",
+ "settings.origin_size": "Pivot Marker",
+ "settings.origin_size.desc": "Size of the pivot point marker",
"settings.control_size": "Axis Control Size",
"settings.control_size.desc": "Size of the 3 axis control tool",
"settings.display_skin": "Display Skin",
@@ -399,9 +403,11 @@
"settings.shading": "Shading",
"settings.shading.desc": "Enable shading",
"settings.transparency": "Transparency",
- "settings.transparency.desc": "Render transparent textures transparent",
+ "settings.transparency.desc": "Render transparent textures transparently",
"settings.texture_fps": "Animated Texture FPS",
"settings.texture_fps.desc": "Frames per second for animated textures",
+ "settings.volume": "Volume",
+ "settings.volume.desc": "Volume control for sound effects in animations",
"settings.outliner_colors": "Outliner Colors",
"settings.outliner_colors.desc": "Display cube colors in the outliner",
@@ -428,7 +434,7 @@
"settings.paint_side_restrict.desc": "Restrict brushes to only paint on the current side",
"settings.autouv": "Auto UV",
- "settings.autouv.desc": "Enable AutoUV by default",
+ "settings.autouv.desc": "Enable Auto UV by default",
"settings.create_rename": "Rename New Cube",
"settings.create_rename.desc": "Focus name field when creating new element or group",
@@ -503,12 +509,12 @@
"action.slider_rotation_y.desc": "Rotate cubes on the Y axis",
"action.slider_rotation_z": "Rotate Z",
"action.slider_rotation_z.desc": "Rotate cubes on the Z axis",
- "action.slider_origin_x": "Origin X",
- "action.slider_origin_x.desc": "Move origin on the X axis",
- "action.slider_origin_y": "Origin Y",
- "action.slider_origin_y.desc": "Move origin on the Y axis",
- "action.slider_origin_z": "Origin Z",
- "action.slider_origin_z.desc": "Move origin on the Z axis",
+ "action.slider_origin_x": "Pivot X",
+ "action.slider_origin_x.desc": "Move pivot on the X axis",
+ "action.slider_origin_y": "Pivot Y",
+ "action.slider_origin_y.desc": "Move pivot on the Y axis",
+ "action.slider_origin_z": "Pivot Z",
+ "action.slider_origin_z.desc": "Move pivot on the Z axis",
"action.brush_mode": "Brush Mode",
"action.brush_mode.desc": "Mode of the brush",
@@ -600,6 +606,8 @@
"action.import_optifine_part.desc": "Import an entity part model for OptiFine",
"action.export_optifine_full": "Export OptiFine JEM",
"action.export_optifine_full.desc": "Export a full OptiFine entity model",
+ "action.export_optifine_part": "Export OptiFine Part",
+ "action.export_optifine_part.desc": "Export a single part for an OptiFine entity model",
"action.export_obj": "Export OBJ Model",
"action.export_obj.desc": "Export a Wavefront OBJ model for rendering or game engines",
"action.upload_sketchfab": "Sketchfab Upload",
@@ -779,8 +787,8 @@
"action.animated_textures": "Play Animated Textures",
"action.animated_textures.desc": "Play and pause the preview of animated textures",
- "action.origin_to_geometry": "Origin To Geometry",
- "action.origin_to_geometry.desc": "Set the origin to the center of the geometry",
+ "action.origin_to_geometry": "Center Pivot",
+ "action.origin_to_geometry.desc": "Set the pivot point to the center of the selection",
"action.rescale_toggle": "Toggle Rescale",
"action.rescale_toggle.desc": "Rescale cubes based on their current rotation",
"action.bone_reset_toggle": "Reset Bone",
@@ -839,8 +847,12 @@
"action.slider_animation_length.desc": "Change the length of the selected animation",
"action.slider_keyframe_time": "Timecode",
"action.slider_keyframe_time.desc": "Change the timecode of the selected keyframes",
+ "action.change_keyframe_file": "Select File",
+ "action.change_keyframe_file.desc": "Select an audio file to preview a sound effect.",
"action.reset_keyframe": "Reset Keyframe",
"action.reset_keyframe.desc": "Reset all values of the selected keyframes",
+ "action.reverse_keyframes": "Reverse Keyframes",
+ "action.reverse_keyframes.desc": "Reverse the order of the selected keyframes",
"action.select_all_keyframes": "Select All Keyframes",
"action.select_all_keyframes.desc": "Select all keyframes of the current bone",
"action.delete_keyframes": "Delete Keyframes",
@@ -851,11 +863,21 @@
"action.previous_keyframe.desc": "Jump to the previous keyframe",
"action.next_keyframe": "Next Keyframe",
"action.next_keyframe.desc": "Jump to the next keyframe",
+ "action.clear_timeline": "Clear Timeline",
+ "action.clear_timeline.desc": "Clear all unselected bones from the timeline",
+ "action.select_effect_animator": "Animate Effects",
+ "action.select_effect_animator.desc": "Opens timeline to add sound and particle effects",
+ "action.timeline_focus": "Channel",
+ "action.timeline_focus.desc": "Select the animation channels to display in the timeline",
+ "action.timeline_focus.all": "All",
"timeline.rotation": "Rotation",
"timeline.position": "Position",
"timeline.scale": "Scale",
+ "timeline.particle": "Particle",
+ "timeline.sound": "Sound",
+ "timeline.effects": "Effects",
"menu.file": "File",
"menu.edit": "Edit",
@@ -1042,7 +1064,6 @@
"display.mirror": "Mirror",
"display.slot": "Slot",
"display.reference": "Reference Model",
- "display.presetname": "Name",
"display.reference.player": "Player",
"display.reference.zombie": "Zombie",
diff --git a/lang/es.json b/lang/es.json
index 142725b5f..a513dec78 100644
--- a/lang/es.json
+++ b/lang/es.json
@@ -106,7 +106,6 @@
"dialog.project.width": "Anchura",
"dialog.project.height": "Altura",
"dialog.texture.title": "Textura",
- "dialog.texture.name": "Nombre",
"dialog.texture.variable": "Variable",
"dialog.texture.namespace": "Espacio del Nombre",
"dialog.texture.folder": "Carpeta",
@@ -150,7 +149,6 @@
"dialog.entitylist.bones": "Huesos",
"dialog.entitylist.cubes": "Cubos",
"dialog.create_texture.title": "Crear Textura",
- "dialog.create_texture.name": "Nombre",
"dialog.create_texture.folder": "Carpeta",
"dialog.create_texture.template": "Plantilla",
"dialog.create_texture.resolution": "Resolución",
@@ -591,7 +589,6 @@
"display.scale": "Reescalado",
"display.slot": "Apartado",
"display.reference": "Modelo de Referencia",
- "display.presetname": "Nombre",
"display.reference.player": "Jugador",
"display.reference.zombie": "Zombie",
"display.reference.armor_stand": "Armor Stand",
@@ -791,9 +788,7 @@
"action.open_backup_folder": "Abrir Carpeta de Backups",
"action.open_backup_folder.desc": "Abre la carpeta de backups de Blockbench",
"switches.mirror": "Invertir UV",
- "Name of the Language you are editing, NOT English": {
- "language_name": "Español"
- },
+ "language_name": "Español",
"message.plugin_reload": "Recargados %0 plugins locales",
"settings.brightness": "Brillo",
"settings.brightness.desc": "Brillo de la previsualización. Por defecto es 50",
@@ -912,7 +907,6 @@
"message.wireframe.disabled": "Visualización de la estructura desactivada",
"dialog.project.box_uv": "UV de Caja",
"dialog.convert_project.title": "Convertir Proyecto",
- "dialog.convert_project.format": "Formato",
"dialog.convert_project.text": "¿Seguro que quieres convertir este proyecto? No puedes deshacer este paso.",
"dialog.create_texture.double_use": "Mantener la Ocupación de Múltiples Texturas",
"dialog.model_stats.title": "Estadísticas del Modelo",
@@ -966,5 +960,28 @@
"panel.element.origin": "Punto de Pivote",
"panel.element.rotation": "Rotación",
"message.canvas_limit_error.title": "Canvas Limit Error",
- "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the origin to prevent this."
+ "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the pivot point to prevent this.",
+ "data.effect": "Effect",
+ "generic.name": "Name",
+ "settings.recent_projects": "Recent Model Cap",
+ "settings.recent_projects.desc": "Maximum number of recent models to remember",
+ "settings.volume": "Volume",
+ "settings.volume.desc": "Volume control for sound effects in animations",
+ "action.change_keyframe_file": "Select File",
+ "action.change_keyframe_file.desc": "Select an audio file to preview a sound effect.",
+ "action.clear_timeline": "Clear Timeline",
+ "action.clear_timeline.desc": "Clear all unselected bones from the timeline",
+ "action.select_effect_animator": "Animate Effects",
+ "action.select_effect_animator.desc": "Opens timeline to add sound and particle effects",
+ "action.timeline_focus": "Channel",
+ "action.timeline_focus.desc": "Select the animation channels to display in the timeline",
+ "action.timeline_focus.all": "All",
+ "timeline.particle": "Particle",
+ "timeline.sound": "Sound",
+ "timeline.effects": "Effects",
+ "data.format": "Format",
+ "format.optifine_part": "OptiFine Part",
+ "format.optifine_part.desc": "JPM part for OptiFine entity models",
+ "action.reverse_keyframes": "Reverse Keyframes",
+ "action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
}
\ No newline at end of file
diff --git a/lang/fr.json b/lang/fr.json
index e9fcb620e..fff7da42c 100644
--- a/lang/fr.json
+++ b/lang/fr.json
@@ -1,5 +1,5 @@
{
- "dialog.ok": "OK",
+ "dialog.ok": "ok",
"dialog.cancel": "Annuler",
"dialog.confirm": "Confirmer",
"dialog.close": "Fermer",
@@ -18,7 +18,7 @@
"keys.shift": "Shift",
"keys.alt": "Alt",
"keys.meta": "Cmd",
- "keys.delete": "Supprimer",
+ "keys.delete": "Delete",
"keys.space": "Espace",
"keys.leftclick": "Clic Gauche",
"keys.middleclick": "Clic molette",
@@ -59,26 +59,26 @@
"message.invalid_model.title": "Fichier du modèle invalide",
"message.invalid_model.message": "Le fichier ne contient pas de données de modèle valide",
"message.child_model_only.title": "Modèle enfant vide",
- "message.child_model_only.message": "Ce fichier a pour parent %0 et ne contient pas de model",
- "message.drag_background.title": "Positionner le fond d'écran",
- "message.drag_background.message": "Glissez l'écran pour changer sa position. Maintenez shift puis glissez verticalement pour changer sa taille.",
- "message.unsaved_textures.title": "Textures non sauvegardées",
- "message.unsaved_textures.message": "Votre modèle a des textures non sauvegardées. Assurez-vous de les enregistrer et de les coller dans votre pack de ressources dans le dossier approprié.",
- "message.model_clipping.title": "Modèle trop grand",
- "message.model_clipping.message": "Votre modèle contient %0 cubes plus grands que la limite de 3x3x3 autorisée par Minecraft. Ce modèle ne fonctionnera pas dans Minecraft. Activer l'option 'Toile limité' pour empêcher cela",
- "message.loose_texture.title": "Importation texture",
- "message.loose_texture.message": "La texture importée n'est pas dans un pack de ressource. Minecraft peut seulement charger des textures dans le dossier textures d'un pack de ressources chargé.",
- "message.loose_texture.change": "Changer le chemin",
- "message.update_res.title": "Résolution de texture",
- "message.update_res.message": "Souhaitez-vous mettre à jour la résolution du projet avec la résolution de cette texture? Cliquez sur 'Annuler' si votre texture a une résolution supérieure à la normale.",
- "message.update_res.update": "Mise à jour",
- "message.bedrock_overwrite_error.message": "Blockbench ne peut pas combiner ce modèle avec l'ancien fichier",
- "message.bedrock_overwrite_error.backup_overwrite": "Créer une sauvegarde et écraser",
- "message.bedrock_overwrite_error.overwrite": "Ecraser",
- "message.close_warning.message": "Voulez-vous enregistrer votre modèle?",
- "message.close_warning.web": "Votre travail actuel sera perdu. Êtes-vous sûr de vouloir quitter?",
- "message.default_textures.title": "Textures par défaut",
- "message.default_textures.message": "Sélectionnez le dossier \"textures\" du pack de ressources par défaut",
+ "message.child_model_only.message": "This file is a child of %0 and does not contain a model.",
+ "message.drag_background.title": "Position Background",
+ "message.drag_background.message": "Drag the background to move its position. Hold shift and drag up and down to change its size.",
+ "message.unsaved_textures.title": "Unsaved Textures",
+ "message.unsaved_textures.message": "Your model has unsaved textures. Make sure to save them and paste them into your resource pack in the correct folder.",
+ "message.model_clipping.title": "Model Too Large",
+ "message.model_clipping.message": "Your model contains %0 cubes that are larger than the 3x3x3 limit allowed by Minecraft. This model will not work in Minecraft.",
+ "message.loose_texture.title": "Texture Import",
+ "message.loose_texture.message": "The imported texture is not contained in a resource pack. Minecraft can only load textures inside the textures folder of a loaded resource pack.",
+ "message.loose_texture.change": "Change Path",
+ "message.update_res.title": "Texture Resolution",
+ "message.update_res.message": "Would you like to update the project resolution to the resolution of this texture? Click 'Cancel' if your texture has a higher resolution than normal.",
+ "message.update_res.update": "Update",
+ "message.bedrock_overwrite_error.message": "Blockbench cannot combine this model with the old file",
+ "message.bedrock_overwrite_error.backup_overwrite": "Create Backup and Overwrite",
+ "message.bedrock_overwrite_error.overwrite": "Overwrite",
+ "message.close_warning.message": "Do you want to save your model?",
+ "message.close_warning.web": "Your current work will be lost. Are you sure you want to quit?",
+ "message.default_textures.title": "Default Textures",
+ "message.default_textures.message": "Select the \"textures\"-folder of the default resource pack",
"message.default_textures.detail": "Extrayez le pack de ressources par défaut de Minecraft à partir du fichier .jar ou de Google et téléchargez-le. Ensuite, localisez le dossier « textures » et ouvrez-le. Blockbench se souviendra de cet emplacement et tentera d'extraire des textures s'il ne parvient pas à les trouver dans le pack de ressources actuel.",
"message.default_textures.select": "Sélectionnez le dossier \"textures\"",
"message.image_editor.title": "Sélectionnez un éditeur d'images",
@@ -106,7 +106,6 @@
"dialog.project.width": "Largeur",
"dialog.project.height": "Hauteur",
"dialog.texture.title": "Texture",
- "dialog.texture.name": "Nom",
"dialog.texture.variable": "Variable",
"dialog.texture.namespace": "Espace de nom",
"dialog.texture.folder": "Dossier",
@@ -150,7 +149,6 @@
"dialog.entitylist.bones": "Os",
"dialog.entitylist.cubes": "Cubes",
"dialog.create_texture.title": "Créer texture",
- "dialog.create_texture.name": "Nom",
"dialog.create_texture.folder": "Dossier",
"dialog.create_texture.template": "Modèle",
"dialog.create_texture.resolution": "Résolution",
@@ -591,7 +589,6 @@
"display.scale": "Échelle",
"display.slot": "Emplacement",
"display.reference": "Modèle de référence",
- "display.presetname": "Nom",
"display.reference.player": "Joueur",
"display.reference.zombie": "Zombie",
"display.reference.armor_stand": "Porte-armure",
@@ -791,9 +788,7 @@
"action.open_backup_folder": "Ouvrir le dossier de sauvegarde",
"action.open_backup_folder.desc": "Ouvre le dossier de sauvegarde de Blockbench",
"switches.mirror": "Miroir UV",
- "Name of the Language you are editing, NOT English": {
- "language_name": "Français"
- },
+ "language_name": "Français",
"message.plugin_reload": "Recharger %0 plugins locaux",
"settings.brightness": "Luminosité",
"settings.brightness.desc": "Luminosité de l'aperçu. La valeur par défaut est 50",
@@ -912,7 +907,6 @@
"message.wireframe.disabled": "Vue filaire désactivée",
"dialog.project.box_uv": "Boîte UV",
"dialog.convert_project.title": "Convertir le projet",
- "dialog.convert_project.format": "Format",
"dialog.convert_project.text": "Êtes-vous sûr de vouloir convertir ce projet ? Vous ne pouvez pas annuler cette étape.",
"dialog.create_texture.double_use": "Conserver l'occupation de textures multiples",
"dialog.model_stats.title": "Statistiques du modèle",
@@ -966,5 +960,28 @@
"panel.element.origin": "Point de pivot",
"panel.element.rotation": "Rotation",
"message.canvas_limit_error.title": "Canvas Limit Error",
- "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the origin to prevent this."
+ "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the pivot point to prevent this.",
+ "data.effect": "Effect",
+ "generic.name": "Name",
+ "settings.recent_projects": "Recent Model Cap",
+ "settings.recent_projects.desc": "Maximum number of recent models to remember",
+ "settings.volume": "Volume",
+ "settings.volume.desc": "Volume control for sound effects in animations",
+ "action.change_keyframe_file": "Select File",
+ "action.change_keyframe_file.desc": "Select an audio file to preview a sound effect.",
+ "action.clear_timeline": "Clear Timeline",
+ "action.clear_timeline.desc": "Clear all unselected bones from the timeline",
+ "action.select_effect_animator": "Animate Effects",
+ "action.select_effect_animator.desc": "Opens timeline to add sound and particle effects",
+ "action.timeline_focus": "Channel",
+ "action.timeline_focus.desc": "Select the animation channels to display in the timeline",
+ "action.timeline_focus.all": "All",
+ "timeline.particle": "Particle",
+ "timeline.sound": "Sound",
+ "timeline.effects": "Effects",
+ "data.format": "Format",
+ "format.optifine_part": "OptiFine Part",
+ "format.optifine_part.desc": "JPM part for OptiFine entity models",
+ "action.reverse_keyframes": "Reverse Keyframes",
+ "action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
}
\ No newline at end of file
diff --git a/lang/it.json b/lang/it.json
index 9369011a2..21cda1a28 100644
--- a/lang/it.json
+++ b/lang/it.json
@@ -106,7 +106,6 @@
"dialog.project.width": "Larghezza",
"dialog.project.height": "Altezza",
"dialog.texture.title": "Texture",
- "dialog.texture.name": "Nome",
"dialog.texture.variable": "Variabile",
"dialog.texture.namespace": "Namespace",
"dialog.texture.folder": "Cartella",
@@ -150,7 +149,6 @@
"dialog.entitylist.bones": "Ossa",
"dialog.entitylist.cubes": "Cubi",
"dialog.create_texture.title": "Creare Texture",
- "dialog.create_texture.name": "Nome",
"dialog.create_texture.folder": "Cartella",
"dialog.create_texture.template": "Sagoma",
"dialog.create_texture.resolution": "Risoluzione",
@@ -591,7 +589,6 @@
"display.scale": "Scala",
"display.slot": "Slot",
"display.reference": "Modello di Riferimento",
- "display.presetname": "Nome",
"display.reference.player": "Giocatore",
"display.reference.zombie": "Zombie",
"display.reference.armor_stand": "Supporto per Armatura",
@@ -791,9 +788,7 @@
"action.open_backup_folder": "Apri Cartella Backup",
"action.open_backup_folder.desc": "Apri la cartella backup di Blockbench",
"switches.mirror": "Specchia UV",
- "Name of the Language you are editing, NOT English": {
- "language_name": "Italiano"
- },
+ "language_name": "Italiano",
"message.plugin_reload": "%0 plugin locali ricaricati",
"settings.brightness": "Luminosità",
"settings.brightness.desc": "Luminosità dell'anteprima. Valore predefinito è 50.",
@@ -912,7 +907,6 @@
"message.wireframe.disabled": "Vista wireframe disabilitata",
"dialog.project.box_uv": "UV Unico",
"dialog.convert_project.title": "Converti Progetto",
- "dialog.convert_project.format": "Formato",
"dialog.convert_project.text": "Sei sicuro di voler convertire questo progetto? Non puoi disfare questa azione.",
"dialog.create_texture.double_use": "Mantenere Occupazione Texture",
"dialog.model_stats.title": "Statistiche Modello",
@@ -966,5 +960,28 @@
"panel.element.origin": "Origine",
"panel.element.rotation": "Rotazione",
"message.canvas_limit_error.title": "Canvas Limit Error",
- "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the origin to prevent this."
+ "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the pivot point to prevent this.",
+ "data.effect": "Effect",
+ "generic.name": "Name",
+ "settings.recent_projects": "Recent Model Cap",
+ "settings.recent_projects.desc": "Maximum number of recent models to remember",
+ "settings.volume": "Volume",
+ "settings.volume.desc": "Volume control for sound effects in animations",
+ "action.change_keyframe_file": "Select File",
+ "action.change_keyframe_file.desc": "Select an audio file to preview a sound effect.",
+ "action.clear_timeline": "Clear Timeline",
+ "action.clear_timeline.desc": "Clear all unselected bones from the timeline",
+ "action.select_effect_animator": "Animate Effects",
+ "action.select_effect_animator.desc": "Opens timeline to add sound and particle effects",
+ "action.timeline_focus": "Channel",
+ "action.timeline_focus.desc": "Select the animation channels to display in the timeline",
+ "action.timeline_focus.all": "All",
+ "timeline.particle": "Particle",
+ "timeline.sound": "Sound",
+ "timeline.effects": "Effects",
+ "data.format": "Format",
+ "format.optifine_part": "OptiFine Part",
+ "format.optifine_part.desc": "JPM part for OptiFine entity models",
+ "action.reverse_keyframes": "Reverse Keyframes",
+ "action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
}
\ No newline at end of file
diff --git a/lang/ja.json b/lang/ja.json
index e4f4c257a..dc76aa166 100644
--- a/lang/ja.json
+++ b/lang/ja.json
@@ -106,7 +106,6 @@
"dialog.project.width": "幅",
"dialog.project.height": "高さ",
"dialog.texture.title": "テクスチャ",
- "dialog.texture.name": "名前",
"dialog.texture.variable": "変数",
"dialog.texture.namespace": "保存場所",
"dialog.texture.folder": "フォルダ",
@@ -150,7 +149,6 @@
"dialog.entitylist.bones": "骨格",
"dialog.entitylist.cubes": "キューブ",
"dialog.create_texture.title": "テクスチャの作成",
- "dialog.create_texture.name": "名前",
"dialog.create_texture.folder": "フォルダ",
"dialog.create_texture.template": "テンプレート",
"dialog.create_texture.resolution": "Resolution",
@@ -591,7 +589,6 @@
"display.scale": "大きさ",
"display.slot": "スロット",
"display.reference": "参照モデル",
- "display.presetname": "名前",
"display.reference.player": "プレイヤー",
"display.reference.zombie": "ゾンビ",
"display.reference.armor_stand": "アーマースタンド",
@@ -791,9 +788,7 @@
"action.open_backup_folder": "Open Backup Folder",
"action.open_backup_folder.desc": "Blockbenchのバックアップフォルダーを開きます",
"switches.mirror": "ミラーUV",
- "Name of the Language you are editing, NOT English": {
- "language_name": "英語"
- },
+ "language_name": "英語",
"message.plugin_reload": "%0プラグインをリロードしました",
"settings.brightness": "輝度",
"settings.brightness.desc": "プレビューの明るさ設定(初期値50)",
@@ -912,7 +907,6 @@
"message.wireframe.disabled": "ワイヤーフレームを無効にしました",
"dialog.project.box_uv": "ボックスUV",
"dialog.convert_project.title": "プロジェクトを変換",
- "dialog.convert_project.format": "フォーマット",
"dialog.convert_project.text": "このプロジェクトを変換しますか?この動作は戻せません",
"dialog.create_texture.double_use": "マルチテクスチャをキープする",
"dialog.model_stats.title": "モデルステータス",
@@ -965,6 +959,29 @@
"panel.element.size": "Size",
"panel.element.origin": "Center",
"panel.element.rotation": "Rotation",
- "message.canvas_limit_error.title": "Canvas Limit Error",
- "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the origin to prevent this."
+ "message.canvas_limit_error.title": "キャンバス制限エラー",
+ "message.canvas_limit_error.message": "フォーマットによってキャンバスが48単位に制限されているため、アクションを正しく実行できませんでした。",
+ "data.effect": "Effect",
+ "generic.name": "Name",
+ "settings.recent_projects": "Recent Model Cap",
+ "settings.recent_projects.desc": "Maximum number of recent models to remember",
+ "settings.volume": "Volume",
+ "settings.volume.desc": "Volume control for sound effects in animations",
+ "action.change_keyframe_file": "Select File",
+ "action.change_keyframe_file.desc": "Select an audio file to preview a sound effect.",
+ "action.clear_timeline": "Clear Timeline",
+ "action.clear_timeline.desc": "Clear all unselected bones from the timeline",
+ "action.select_effect_animator": "Animate Effects",
+ "action.select_effect_animator.desc": "Opens timeline to add sound and particle effects",
+ "action.timeline_focus": "Channel",
+ "action.timeline_focus.desc": "Select the animation channels to display in the timeline",
+ "action.timeline_focus.all": "All",
+ "timeline.particle": "Particle",
+ "timeline.sound": "Sound",
+ "timeline.effects": "Effects",
+ "data.format": "Format",
+ "format.optifine_part": "OptiFine Part",
+ "format.optifine_part.desc": "JPM part for OptiFine entity models",
+ "action.reverse_keyframes": "Reverse Keyframes",
+ "action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
}
\ No newline at end of file
diff --git a/lang/nl.json b/lang/nl.json
index 2592c3056..3286eda44 100644
--- a/lang/nl.json
+++ b/lang/nl.json
@@ -106,7 +106,6 @@
"dialog.project.width": "Breedte",
"dialog.project.height": "Lengte",
"dialog.texture.title": "Textuur",
- "dialog.texture.name": "Naam",
"dialog.texture.variable": "Variabele",
"dialog.texture.namespace": "Naamruimte",
"dialog.texture.folder": "Map",
@@ -150,7 +149,6 @@
"dialog.entitylist.bones": "Botten",
"dialog.entitylist.cubes": "Kubussen",
"dialog.create_texture.title": "Maak Afbeelding",
- "dialog.create_texture.name": "Naam",
"dialog.create_texture.folder": "Map",
"dialog.create_texture.template": "Sjabloon",
"dialog.create_texture.resolution": "Resolutie",
@@ -591,7 +589,6 @@
"display.scale": "Schaal",
"display.slot": "Slot",
"display.reference": "Verwijs Naar Model",
- "display.presetname": "Naam",
"display.reference.player": "Speler",
"display.reference.zombie": "Zombie",
"display.reference.armor_stand": "Armor Stand",
@@ -738,20 +735,20 @@
"action.select_all_keyframes.desc": "Select all keyframes of the current bone",
"action.delete_keyframes": "Delete Keyframes",
"action.delete_keyframes.desc": "Delete all selected keyframes",
- "menu.animation": "Animation",
- "menu.animation.loop": "Loop",
- "menu.animation.override": "Override",
- "menu.animation.anim_time_update": "Update Variable",
+ "menu.animation": "Animatie",
+ "menu.animation.loop": "Herhaal",
+ "menu.animation.override": "Overschrijf",
+ "menu.animation.anim_time_update": "Update Nummer",
"message.display_skin_model.title": "Skin Model",
- "message.display_skin_model.message": "Chose the model type of your skin",
- "message.display_skin_model.classic": "Classic",
- "message.display_skin_model.slim": "Slim",
- "message.bone_material": "Change bone material",
- "action.slider_animation_length": "Animation Length",
- "action.slider_animation_length.desc": "Change the length of the selected animation",
- "menu.group.material": "Set Material",
- "action.camera_reset": "Reset Camera",
- "action.camera_reset.desc": "Reset the current preview to the default camera angle",
+ "message.display_skin_model.message": "Kies je type skin",
+ "message.display_skin_model.classic": "Klassiek (steve)",
+ "message.display_skin_model.slim": "Slim (alex)",
+ "message.bone_material": "Verander materiaal",
+ "action.slider_animation_length": "Animatie Lengte",
+ "action.slider_animation_length.desc": "Verander de lengte van de geselecteerde animatie",
+ "menu.group.material": "Zet Materiaal",
+ "action.camera_reset": "Reset Camera Positie",
+ "action.camera_reset.desc": "Reset het perspectief naar de standaard camera positie",
"panel.variable_placeholders": "Variable Placeholders",
"panel.variable_placeholders.info": "List the variables you want to preview via name=value",
"status_bar.vertex_distance": "Distance: %0",
@@ -767,33 +764,31 @@
"data.separator": "Scheidingsteken",
"message.set_background_position.title": "Background Position",
"menu.preview.background.set_position": "Set Position",
- "dialog.toolbar_edit.hidden": "Hidden",
- "action.export_class_entity": "Export Java Entity",
+ "dialog.toolbar_edit.hidden": "Verborgen",
+ "action.export_class_entity": "Exporteer Java Editie Entiteit",
"action.export_class_entity.desc": "Export the entity model as a Java class",
- "settings.seethrough_outline": "X-Ray Outlines",
- "settings.seethrough_outline.desc": "Show outlines through objects",
- "mode.edit": "Edit",
- "mode.paint": "Paint",
- "mode.display": "Display",
- "mode.animate": "Animate",
- "status_bar.recording_gif": "Recording GIF",
- "status_bar.processing_gif": "Processing GIF",
- "settings.backup_retain": "Backup Retain Duration",
- "settings.backup_retain.desc": "Set how long Blockbench retains old backups in days",
- "action.rotate_tool": "Rotate",
+ "settings.seethrough_outline": "X-Ray Buitenlijnen",
+ "settings.seethrough_outline.desc": "Laat buitenlijnen door voorwerpen heen zien",
+ "mode.edit": "Verander",
+ "mode.paint": "Verf",
+ "mode.display": "Positie",
+ "mode.animate": "Animeer",
+ "status_bar.recording_gif": "Opnemen GIF",
+ "status_bar.processing_gif": "Verwerken GIF",
+ "settings.backup_retain": "Backup Ophalen Duratie",
+ "settings.backup_retain.desc": "Stel in hoe lang Blockbench oude updates ophaalt in dagen",
+ "action.rotate_tool": "Roteer",
"action.rotate_tool.desc": "Tool to select and rotate elements",
- "action.fill_tool": "Paint Bucket",
+ "action.fill_tool": "Verf Emmer",
"action.fill_tool.desc": "Paint bucket to fill entire faces with one color",
- "action.eraser": "Eraser",
+ "action.eraser": "Gum",
"action.eraser.desc": "Eraser tool to replace colors on a texture with transparency",
- "action.color_picker": "Color Picker",
+ "action.color_picker": "Kleur Pipet",
"action.color_picker.desc": "Tool to pick the color of pixels on your texture",
"action.open_backup_folder": "Open Backup Folder",
"action.open_backup_folder.desc": "Opens the Blockbench backup folder",
"switches.mirror": "Mirror UV",
- "Name of the Language you are editing, NOT English": {
- "language_name": "Nederlands"
- },
+ "language_name": "Nederlands",
"message.plugin_reload": "Reloaded %0 local plugins",
"settings.brightness": "Brightness",
"settings.brightness.desc": "Brightness of the preview. Default is 50",
@@ -837,7 +832,7 @@
"settings.sketchfab_token": "Sketchfab Token",
"settings.sketchfab_token.desc": "Token to authorize Blockbench to upload to your Sketchfab account",
"panel.color": "Color",
- "data.origin": "Origin",
+ "data.origin": "Pivot",
"message.sketchfab.success": "Uploaded model successfully",
"message.sketchfab.error": "Failed to upload model to Sketchfab",
"settings.outliner_colors": "Outliner Colors",
@@ -912,7 +907,6 @@
"message.wireframe.disabled": "Wireframe view disabled",
"dialog.project.box_uv": "Box UV",
"dialog.convert_project.title": "Convert Project",
- "dialog.convert_project.format": "Format",
"dialog.convert_project.text": "Are you sure you want to convert this project? You cannot undo this step.",
"dialog.create_texture.double_use": "Keep Multi Texture Occupancy",
"dialog.model_stats.title": "Model Stats",
@@ -966,5 +960,28 @@
"panel.element.origin": "Pivot Point",
"panel.element.rotation": "Rotation",
"message.canvas_limit_error.title": "Canvas Limit Error",
- "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the origin to prevent this."
+ "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the pivot point to prevent this.",
+ "data.effect": "Effect",
+ "generic.name": "Name",
+ "settings.recent_projects": "Recent Model Cap",
+ "settings.recent_projects.desc": "Maximum number of recent models to remember",
+ "settings.volume": "Volume",
+ "settings.volume.desc": "Volume control for sound effects in animations",
+ "action.change_keyframe_file": "Select File",
+ "action.change_keyframe_file.desc": "Select an audio file to preview a sound effect.",
+ "action.clear_timeline": "Clear Timeline",
+ "action.clear_timeline.desc": "Clear all unselected bones from the timeline",
+ "action.select_effect_animator": "Animate Effects",
+ "action.select_effect_animator.desc": "Opens timeline to add sound and particle effects",
+ "action.timeline_focus": "Channel",
+ "action.timeline_focus.desc": "Select the animation channels to display in the timeline",
+ "action.timeline_focus.all": "All",
+ "timeline.particle": "Particle",
+ "timeline.sound": "Sound",
+ "timeline.effects": "Effects",
+ "data.format": "Format",
+ "format.optifine_part": "OptiFine Part",
+ "format.optifine_part.desc": "JPM part for OptiFine entity models",
+ "action.reverse_keyframes": "Reverse Keyframes",
+ "action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
}
\ No newline at end of file
diff --git a/lang/pl.json b/lang/pl.json
index 4d60afd49..c5b1adac2 100644
--- a/lang/pl.json
+++ b/lang/pl.json
@@ -106,7 +106,6 @@
"dialog.project.width": "Szerokość",
"dialog.project.height": "Wysokość",
"dialog.texture.title": "Tekstura",
- "dialog.texture.name": "Nazwa",
"dialog.texture.variable": "Zmienna",
"dialog.texture.namespace": "Przestrzeń nazw",
"dialog.texture.folder": "Folder",
@@ -150,7 +149,6 @@
"dialog.entitylist.bones": "Kości",
"dialog.entitylist.cubes": "Kostki",
"dialog.create_texture.title": "Stwórz teksturę",
- "dialog.create_texture.name": "Nazwa",
"dialog.create_texture.folder": "Folder",
"dialog.create_texture.template": "Szablon",
"dialog.create_texture.resolution": "Rozdzielczość",
@@ -591,7 +589,6 @@
"display.scale": "Skala",
"display.slot": "Slot",
"display.reference": "Model referencyjny",
- "display.presetname": "Nazwa",
"display.reference.player": "Gracz",
"display.reference.zombie": "Zombie",
"display.reference.armor_stand": "Stojak na zbroje",
@@ -791,9 +788,7 @@
"action.open_backup_folder": "Otwórz folder kopii zapasowej",
"action.open_backup_folder.desc": "Otwórz folder kopii zapasowej BlockBench",
"switches.mirror": "Lustro UV",
- "Name of the Language you are editing, NOT English": {
- "language_name": "Polski"
- },
+ "language_name": "Polski",
"message.plugin_reload": "Reloaded %0 local plugins",
"settings.brightness": "Brightness",
"settings.brightness.desc": "Brightness of the preview. Default is 50",
@@ -837,7 +832,7 @@
"settings.sketchfab_token": "Sketchfab Token",
"settings.sketchfab_token.desc": "Token to authorize Blockbench to upload to your Sketchfab account",
"panel.color": "Color",
- "data.origin": "Origin",
+ "data.origin": "Pivot",
"message.sketchfab.success": "Uploaded model successfully",
"message.sketchfab.error": "Failed to upload model to Sketchfab",
"settings.outliner_colors": "Outliner Colors",
@@ -912,7 +907,6 @@
"message.wireframe.disabled": "Wireframe view disabled",
"dialog.project.box_uv": "Box UV",
"dialog.convert_project.title": "Convert Project",
- "dialog.convert_project.format": "Format",
"dialog.convert_project.text": "Are you sure you want to convert this project? You cannot undo this step.",
"dialog.create_texture.double_use": "Keep Multi Texture Occupancy",
"dialog.model_stats.title": "Model Stats",
@@ -966,5 +960,28 @@
"panel.element.origin": "Pivot Point",
"panel.element.rotation": "Rotation",
"message.canvas_limit_error.title": "Canvas Limit Error",
- "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the origin to prevent this."
+ "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the pivot point to prevent this.",
+ "data.effect": "Effect",
+ "generic.name": "Name",
+ "settings.recent_projects": "Recent Model Cap",
+ "settings.recent_projects.desc": "Maximum number of recent models to remember",
+ "settings.volume": "Volume",
+ "settings.volume.desc": "Volume control for sound effects in animations",
+ "action.change_keyframe_file": "Select File",
+ "action.change_keyframe_file.desc": "Select an audio file to preview a sound effect.",
+ "action.clear_timeline": "Clear Timeline",
+ "action.clear_timeline.desc": "Clear all unselected bones from the timeline",
+ "action.select_effect_animator": "Animate Effects",
+ "action.select_effect_animator.desc": "Opens timeline to add sound and particle effects",
+ "action.timeline_focus": "Channel",
+ "action.timeline_focus.desc": "Select the animation channels to display in the timeline",
+ "action.timeline_focus.all": "All",
+ "timeline.particle": "Particle",
+ "timeline.sound": "Sound",
+ "timeline.effects": "Effects",
+ "data.format": "Format",
+ "format.optifine_part": "OptiFine Part",
+ "format.optifine_part.desc": "JPM part for OptiFine entity models",
+ "action.reverse_keyframes": "Reverse Keyframes",
+ "action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
}
\ No newline at end of file
diff --git a/lang/pt.json b/lang/pt.json
index b3547cd6c..9f2fae6ed 100644
--- a/lang/pt.json
+++ b/lang/pt.json
@@ -106,7 +106,6 @@
"dialog.project.width": "Largura",
"dialog.project.height": "Altura",
"dialog.texture.title": "Textura",
- "dialog.texture.name": "Nome",
"dialog.texture.variable": "Variável",
"dialog.texture.namespace": "Ambiente",
"dialog.texture.folder": "Pasta",
@@ -150,7 +149,6 @@
"dialog.entitylist.bones": "Ossos",
"dialog.entitylist.cubes": "Cubos",
"dialog.create_texture.title": "Criar textura",
- "dialog.create_texture.name": "Nome",
"dialog.create_texture.folder": "Pasta",
"dialog.create_texture.template": "Modelo",
"dialog.create_texture.resolution": "Resolução",
@@ -591,7 +589,6 @@
"display.scale": "Redimensionar",
"display.slot": "Slot",
"display.reference": "Modelo de Referência",
- "display.presetname": "Nome",
"display.reference.player": "Jogador",
"display.reference.zombie": "Zumbi",
"display.reference.armor_stand": "Suporte de Armadura",
@@ -791,9 +788,7 @@
"action.open_backup_folder": "Abrir Pasta de Backup",
"action.open_backup_folder.desc": "Abre a pasta de backup do Blockbench",
"switches.mirror": "Espelhar UV",
- "Name of the Language you are editing, NOT English": {
- "language_name": "Português"
- },
+ "language_name": "Português",
"message.plugin_reload": "Recarregados %0 plugins locais",
"settings.brightness": "Brilho",
"settings.brightness.desc": "Brilho da pré visualização. Padrão é 50",
@@ -912,7 +907,6 @@
"message.wireframe.disabled": "Wireframe view disabled",
"dialog.project.box_uv": "Box UV",
"dialog.convert_project.title": "Convert Project",
- "dialog.convert_project.format": "Format",
"dialog.convert_project.text": "Are you sure you want to convert this project? You cannot undo this step.",
"dialog.create_texture.double_use": "Keep Multi Texture Occupancy",
"dialog.model_stats.title": "Model Stats",
@@ -966,5 +960,28 @@
"panel.element.origin": "Pivot Point",
"panel.element.rotation": "Rotation",
"message.canvas_limit_error.title": "Canvas Limit Error",
- "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the origin to prevent this."
+ "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the pivot point to prevent this.",
+ "data.effect": "Effect",
+ "generic.name": "Name",
+ "settings.recent_projects": "Recent Model Cap",
+ "settings.recent_projects.desc": "Maximum number of recent models to remember",
+ "settings.volume": "Volume",
+ "settings.volume.desc": "Volume control for sound effects in animations",
+ "action.change_keyframe_file": "Select File",
+ "action.change_keyframe_file.desc": "Select an audio file to preview a sound effect.",
+ "action.clear_timeline": "Clear Timeline",
+ "action.clear_timeline.desc": "Clear all unselected bones from the timeline",
+ "action.select_effect_animator": "Animate Effects",
+ "action.select_effect_animator.desc": "Opens timeline to add sound and particle effects",
+ "action.timeline_focus": "Channel",
+ "action.timeline_focus.desc": "Select the animation channels to display in the timeline",
+ "action.timeline_focus.all": "All",
+ "timeline.particle": "Particle",
+ "timeline.sound": "Sound",
+ "timeline.effects": "Effects",
+ "data.format": "Format",
+ "format.optifine_part": "OptiFine Part",
+ "format.optifine_part.desc": "JPM part for OptiFine entity models",
+ "action.reverse_keyframes": "Reverse Keyframes",
+ "action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
}
\ No newline at end of file
diff --git a/lang/ru.json b/lang/ru.json
index b122e906c..743536123 100644
--- a/lang/ru.json
+++ b/lang/ru.json
@@ -3,7 +3,7 @@
"dialog.cancel": "Отмена",
"dialog.confirm": "Подтвердить",
"dialog.close": "Закрыть",
- "dialog.import": "Импортировать",
+ "dialog.import": "Импорт",
"dialog.save": "Сохранить",
"dialog.discard": "Не сохранять",
"dialog.dontshowagain": "Не показывать снова",
@@ -14,22 +14,22 @@
"data.preview": "Предпросмотр",
"data.toolbar": "Инструменты",
"data.image": "Изображение",
- "keys.ctrl": "Ctrl",
+ "keys.ctrl": "Control",
"keys.shift": "Shift",
"keys.alt": "Alt",
"keys.meta": "Cmd",
"keys.delete": "Delete",
"keys.space": "Пробел",
"keys.leftclick": "ЛКМ",
- "keys.middleclick": "СКМ",
+ "keys.middleclick": "КМ",
"keys.rightclick": "ПКМ",
"keys.tab": "Tab",
"keys.backspace": "Backspace",
- "keys.enter": "Enter",
+ "keys.enter": "Ввод",
"keys.escape": "Escape",
"keys.function": "F%0",
"keys.numpad": "%0 (цифр. кл.)",
- "keys.caps": "Caps Lock",
+ "keys.caps": "Capslock",
"keys.menu": "Меню",
"keys.left": "Стрелка влево",
"keys.up": "Стрелка вверх",
@@ -43,11 +43,11 @@
"keys.minus": "-",
"keys.cross": "Крестик",
"keys.end": "End",
- "keys.pos1": "Home",
+ "keys.pos1": "Pos 1",
"keys.printscreen": "Print Screen",
"keys.pause": "Pause",
"message.rotation_limit.title": "Ограничение поворота",
- "message.rotation_limit.message": "Поворот ограничен до 22,5 градусов Майнкрафтом по каждой из осей. Поворот на разных осях сбросит повороты на других. Отключите функцию \"Ограничение поворота\", если вы создаете модель для других целей, где нужен свободный поворот.",
+ "message.rotation_limit.message": "Поворот ограничен Minecraft до одной оси и 22,5 градусов. Поворот на разных осях сбросит все повороты на других осях. Конвертируйте модель в \"свободную\", если вы создаете модель для других целей, где нужен свободный поворот.",
"message.file_not_found.title": "Файл не найден",
"message.file_not_found.message": "Blockbench не может найти запрашиваемый файл. Убедитесь, что он сохранен по указанному пути, не в облаке.",
"message.screenshot.title": "Скриншот",
@@ -55,7 +55,7 @@
"message.screenshot.clipboard": "Скопировать",
"message.screenshot.right_click": "Скриншот: ПКМ — скопировать",
"message.invalid_file.title": "Недопустимый файл",
- "message.invalid_file.message": "Не удалось открыть модель: %0",
+ "message.invalid_file.message": "Не удалось открыть файл: %0",
"message.invalid_model.title": "Недопустимый файл модели",
"message.invalid_model.message": "Этот файл содержит недопустимые данные модели.",
"message.child_model_only.title": "Пустая дочерняя модель",
@@ -63,39 +63,39 @@
"message.drag_background.title": "Расположение фона",
"message.drag_background.message": "Нажмите и перетаскивайте фон, чтобы изменить его положение. Удерживайте Shift для изменения размера.",
"message.unsaved_textures.title": "Несохранённые текстуры",
- "message.unsaved_textures.message": "Ваша модель содержит несохранённые текстуры. Убедитесь, что они сохранены и помещены в пакет ресурсов.",
+ "message.unsaved_textures.message": "Ваша модель содержит несохранённые текстуры. Убедитесь, что они сохранены и помещены в ваш набор ресурсов в правильной папке.",
"message.model_clipping.title": "Модель слишком большая",
- "message.model_clipping.message": "Модель содержит %0 кубов, которые превысили ограничение размера моделей в Minecraft — 3x3x3. Модель не будет работать в игре. Включите функцию «Ограниченный холст», чтобы этого избежать.",
+ "message.model_clipping.message": "Ваша модель содержит %0 кубов, которые больше чем ограничение 3x3x3. Эта модель не будет работать в Minecraft.",
"message.loose_texture.title": "Импортирование текстуры",
- "message.loose_texture.message": "Импортированная текстура не находится в пакете ресурсов. Игра может использовать только текстуры, находящиеся в папке текстур загруженного пакета ресурсов.",
+ "message.loose_texture.message": "Импортированная текстура не находится в наборе ресурсов. Minecraft может загружать только текстуры, находящиеся в папке текстур загруженного набора ресурсов.",
"message.loose_texture.change": "Изменить путь",
"message.update_res.title": "Разрешение текстуры",
- "message.update_res.message": "Хотите ли вы обновить разрешение проекта в соответствии с разрешением текстуры? Нажмите «Отмена», если разрешение вашей текстуры больше.",
+ "message.update_res.message": "Хотите ли вы обновить разрешение проекта на разрешение этой текстуры? Нажмите «Отмена», если ваша текстура имеет разрешение больше нормального.",
"message.update_res.update": "Обновить",
"message.bedrock_overwrite_error.message": "Blockbench не может совместить эту модель со старым файлом.",
"message.bedrock_overwrite_error.backup_overwrite": "Создать копию и заменить",
"message.bedrock_overwrite_error.overwrite": "Заменить",
- "message.close_warning.message": "Вы хотите сохранить модель?",
+ "message.close_warning.message": "Вы хотите сохранить вашу модель?",
"message.close_warning.web": "Ваша работа будет утеряна. Вы уверены, что хотите выйти?",
"message.default_textures.title": "Стандартные текстуры",
- "message.default_textures.message": "Выберите папку «textures» стандартного пакета ресурсов.",
- "message.default_textures.detail": "Извлеките стандартный пакет ресурсов из JAR-файла Minecraft или поищите его в интернете. Затем найдите папку «textures» и выберите ее. Blockbench запомнит её расположение и будет загружать текстуры из неё, если не сможет найти их в текущем пакете ресурсов.",
+ "message.default_textures.message": "Выберите папку «textures» стандартного набора ресурсов.",
+ "message.default_textures.detail": "Извлеките стандартный набор ресурсов из Minecraft jar или поищите его в интернете. Затем найдите папку «textures» и откройте ее. Blockbench запомнит её расположение и будет загружать текстуры из неё, если не сможет найти их в текущем наборе ресурсов.",
"message.default_textures.select": "Выбрать папку «textures»",
"message.image_editor.title": "Выбор редактора изображений",
"message.image_editor.file": "Выбрать файл...",
- "message.image_editor.exe": "Выберите исполняемый файл редактора",
+ "message.image_editor.exe": "Выберите исполняемый файл редактора изображений",
"message.display_skin.title": "Отображаемый скин",
"message.display_skin.message": "Выберите скин на вашем компьютере или введите имя игрока",
"message.display_skin.upload": "Загрузить скин",
- "message.display_skin.name": "Ввести имя",
- "message.display_skin.reset": "Сбросить",
+ "message.display_skin.name": "Имя",
+ "message.display_skin.reset": "Сброс",
"message.invalid_plugin": "Неверный файл плагина, посмотрите в консоль",
- "message.load_plugin_app": "Разрешаете ли вы изменять плагину файлы на вашем компьютере? Загружайте плагины те, которым доверяете.",
- "message.load_plugin_web": "Вы хотите загрузить этот плагин? Загружайте плагины те, которым доверяете.",
+ "message.load_plugin_app": "Хотите ли вы разрешить этому плагину изменять файлы на вашем компьютере? Загружайте только те плагины, которым вы доверяете.",
+ "message.load_plugin_web": "Вы хотите загрузить этот плагин? Загружайте плагины только от людей, которым вы доверяете.",
"message.preset_no_info": "Шаблон не содержит информации для этого слота.",
"message.restart_to_update": "Перезапустите Blockbench, чтобы применить изменения",
"message.save_file": "Сохранено как %0",
- "message.save_obj": "Сохранено как файл OBJ",
+ "message.save_obj": "Сохранено как .obj модель",
"message.rename_cubes": "Переименовать кубы",
"dialog.project.title": "Проект",
"dialog.project.name": "Имя файла",
@@ -103,10 +103,9 @@
"dialog.project.geoname": "Имя геометрий моба",
"dialog.project.openparent": "Открыть родительскую модель",
"dialog.project.ao": "Мягкое освещение",
- "dialog.project.width": "Ширина",
- "dialog.project.height": "Высота",
+ "dialog.project.width": "Ширина текстуры",
+ "dialog.project.height": "Высота текстуры",
"dialog.texture.title": "Текстура",
- "dialog.texture.name": "Имя файла",
"dialog.texture.variable": "Переменная",
"dialog.texture.namespace": "Пространство имён",
"dialog.texture.folder": "Папка",
@@ -150,7 +149,6 @@
"dialog.entitylist.bones": "Костей",
"dialog.entitylist.cubes": "Кубов",
"dialog.create_texture.title": "Создать текстуру",
- "dialog.create_texture.name": "Имя файла",
"dialog.create_texture.folder": "Папка",
"dialog.create_texture.template": "Шаблон",
"dialog.create_texture.resolution": "Разрешение",
@@ -240,7 +238,7 @@
"settings.canvas_unselect": "Снятие выделения при нажатии по фону",
"settings.canvas_unselect.desc": "Снимать выделение со всех элементов при нажатии по фону рабочей области",
"settings.paint_side_restrict": "Ограничить кисть до грани",
- "settings.paint_side_restrict.desc": "Ограничить кисть до выбранной грани",
+ "settings.paint_side_restrict.desc": "Ограничить кисть до окраски текущей грани",
"settings.autouv": "Авто UV",
"settings.autouv.desc": "Применять «Авто UV» по умолчанию",
"settings.create_rename": "Переименовывание новых кубов",
@@ -336,9 +334,9 @@
"action.resize_tool": "Изменение размера",
"action.resize_tool.desc": "Инструмент для выделения и изменения размера элементов",
"action.brush_tool": "Кисть",
- "action.brush_tool.desc": "Инструмент для рисования на текстурах, на гранях кубов или в редакторе UV",
+ "action.brush_tool.desc": "Инструмент для рисования на текстурах на поверхностях или в редакторе UV",
"action.vertex_snap_tool": "Вершинная привязка",
- "action.vertex_snap_tool.desc": "Перемещение одного куба к другому, соединяя две их вершины",
+ "action.vertex_snap_tool.desc": "Переместить один куб к другому, соединяя две вершины",
"action.swap_tools": "Смена инструмента",
"action.swap_tools.desc": "Переключение между инструментом перемещения и изменения размера",
"action.project_window": "Проект...",
@@ -348,7 +346,7 @@
"action.new_entity_model": "Новая модель сущности",
"action.new_entity_model.desc": "Создаёт новую модель сущности (Bedrock)",
"action.open_model": "Открыть модель",
- "action.open_model.desc": "Открыть файл модели на компьютере",
+ "action.open_model.desc": "Открыть файл модели с вашего компьютере",
"action.add_model": "Добавить модель",
"action.add_model.desc": "Добавить модель из файла к текущей модели",
"action.extrude_texture": "Сканирование изображения",
@@ -357,12 +355,12 @@
"action.export_blockmodel.desc": "Экспортировать модель блока/предмета",
"action.export_entity": "Экспортировать сущность (Bedrock)",
"action.export_entity.desc": "Добавить текущую модель как сущность в файл mobs.json",
- "action.export_optifine_part": "Экспортировать в OptiFine JPM",
+ "action.export_optifine_part": "Экспортировать OptiFine JPM",
"action.export_optifine_part.desc": "Экспортировать часть сущности для OptiFine",
- "action.export_optifine_full": "Экспортировать в OptiFine JEM",
+ "action.export_optifine_full": "Экспортировать OptiFine JEM",
"action.export_optifine_full.desc": "Экспортировать полную модель для OptiFine",
"action.export_obj": "Экспортировать в OBJ",
- "action.export_obj.desc": "Экспортировать модель Wavefront OBJ для использования в других программах или для загрузки на Sketchfab",
+ "action.export_obj.desc": "Экспортировать obj модель для рендеринга или игровых движков",
"action.settings_window": "Настройки...",
"action.settings_window.desc": "Открыть окно настроек Blockbench",
"action.plugins_window": "Плагины...",
@@ -380,7 +378,7 @@
"action.load_plugin": "Загрузить плагин из файла",
"action.load_plugin.desc": "Загрузить плагин, импортировав файл",
"action.reload_plugins": "Перезагрузить плагины",
- "action.reload_plugins.desc": "Перезагрузить все плагины в разработке",
+ "action.reload_plugins.desc": "Перезагрузить все плагины в разработке.",
"action.uv_dialog": "Окно UV",
"action.uv_dialog.desc": "Окно UV со всеми гранями",
"action.uv_dialog_full": "Полный вид",
@@ -488,7 +486,7 @@
"action.rescale_toggle": "Переключить масштабирование",
"action.rescale_toggle.desc": "Масштабировать кубы на основе их поворота",
"action.bone_reset_toggle": "Сбросить кость",
- "action.bone_reset_toggle.desc": "Остановить просмотр кубов родительской модели",
+ "action.bone_reset_toggle.desc": "Остановить часть от отображения кубов родительской модели",
"action.reload": "Перезагрузить Blockbench",
"action.reload.desc": "Перезагрузить Blockbench. Это удалит весь несохранённый прогресс.",
"menu.file": "Файл",
@@ -591,7 +589,6 @@
"display.scale": "Масштаб",
"display.slot": "Слот",
"display.reference": "Эталонная модель",
- "display.presetname": "Название",
"display.reference.player": "Игрок",
"display.reference.zombie": "Зомби",
"display.reference.armor_stand": "Стойка для брони",
@@ -649,7 +646,7 @@
"action.uv_maximize.desc": "Устанавливает UV этой грани на полную текстуру",
"action.uv_auto": "Авто UV",
"action.uv_auto.desc": "Устанавливает UV этой грани на реальный размер грани",
- "action.uv_rel_auto": "Относительное авто UV",
+ "action.uv_rel_auto": "Относительные авто UV",
"action.uv_rel_auto.desc": "Устанавливает UV этой грани на позицию и размер текущей грани",
"action.uv_mirror_x": "Отразить UV на оси X",
"action.uv_mirror_x.desc": "Отражает UV этой грани на оси X",
@@ -685,7 +682,7 @@
"uv_editor.tint_on": "Тон включен",
"uv_editor.tint_off": "Тон выключен",
"action.uv_apply_all": "Применить ко всем граням",
- "action.uv_apply_all.desc": "Применяет настройки текущей грани на всех гранях",
+ "action.uv_apply_all.desc": "Применяет настройки текущей грани всем граням",
"message.image_editor_missing.title": "Редактор изображений по умолчанию",
"message.image_editor_missing.message": "Выберите exe файл Вашего редактора изображений",
"message.image_editor_missing.detail": "Blockbench не смог найти редактор изображений на Вашем компьютере. Выберите exe файл Вашего редактора изображений.",
@@ -791,9 +788,7 @@
"action.open_backup_folder": "Открыть папку автосохранений",
"action.open_backup_folder.desc": "Открыть папку автосохранений Blockbench",
"switches.mirror": "Зеркалить UV",
- "Name of the Language you are editing, NOT English": {
- "language_name": "Русский"
- },
+ "language_name": "Русский",
"message.plugin_reload": "Перезагружено %0 локальных плагинов",
"settings.brightness": "Яркость",
"settings.brightness.desc": "Яркость дисплея. 50 по умолчанию",
@@ -912,7 +907,6 @@
"message.wireframe.disabled": "Каркас выключен",
"dialog.project.box_uv": "Прямоугольный UV",
"dialog.convert_project.title": "Конвертировать проект",
- "dialog.convert_project.format": "Формат",
"dialog.convert_project.text": "Вы уверены что хотите конвертировать этот проект? Вы не можете отменить это действие.",
"dialog.create_texture.double_use": "Сохранять многократное использование текстур",
"dialog.model_stats.title": "Статистика модели",
@@ -958,13 +952,36 @@
"settings.animation_snap.desc": "Интервал привязки кадров на графике анимаций",
"action.import_optifine_part": "Импортировать часть OptiFine",
"action.import_optifine_part.desc": "Импортировать часть модели сущности OptiFine",
- "data.locator": "Маркер",
+ "data.locator": "Локатор",
"mode.start.no_recents": "Вы еще не открывали моделей",
"panel.element": "Элемент",
"panel.element.position": "Позиция",
"panel.element.size": "Размер",
"panel.element.origin": "Центральная точка",
"panel.element.rotation": "Поворот",
- "message.canvas_limit_error.title": "Canvas Limit Error",
- "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the origin to prevent this."
+ "message.canvas_limit_error.title": "Ошибка ограничения рабочей области",
+ "message.canvas_limit_error.message": "Действие не может быть выполнено правильно, потому что формат ограничивает размеры рабочей области до 48 единиц. Сместите модель, чтобы предотвратить это.",
+ "data.effect": "Effect",
+ "generic.name": "Name",
+ "settings.recent_projects": "Recent Model Cap",
+ "settings.recent_projects.desc": "Maximum number of recent models to remember",
+ "settings.volume": "Volume",
+ "settings.volume.desc": "Volume control for sound effects in animations",
+ "action.change_keyframe_file": "Select File",
+ "action.change_keyframe_file.desc": "Select an audio file to preview a sound effect.",
+ "action.clear_timeline": "Clear Timeline",
+ "action.clear_timeline.desc": "Clear all unselected bones from the timeline",
+ "action.select_effect_animator": "Animate Effects",
+ "action.select_effect_animator.desc": "Opens timeline to add sound and particle effects",
+ "action.timeline_focus": "Channel",
+ "action.timeline_focus.desc": "Select the animation channels to display in the timeline",
+ "action.timeline_focus.all": "All",
+ "timeline.particle": "Particle",
+ "timeline.sound": "Sound",
+ "timeline.effects": "Effects",
+ "data.format": "Format",
+ "format.optifine_part": "OptiFine Part",
+ "format.optifine_part.desc": "JPM part for OptiFine entity models",
+ "action.reverse_keyframes": "Reverse Keyframes",
+ "action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
}
\ No newline at end of file
diff --git a/lang/sv.json b/lang/sv.json
index 99d31c146..bf177c54a 100644
--- a/lang/sv.json
+++ b/lang/sv.json
@@ -106,7 +106,6 @@
"dialog.project.width": "Bredd",
"dialog.project.height": "Höjd",
"dialog.texture.title": "Textur",
- "dialog.texture.name": "Namn",
"dialog.texture.variable": "Variabel",
"dialog.texture.namespace": "Namnrymd",
"dialog.texture.folder": "Mapp",
@@ -150,7 +149,6 @@
"dialog.entitylist.bones": "Ben",
"dialog.entitylist.cubes": "Kuber",
"dialog.create_texture.title": "Skapa textur",
- "dialog.create_texture.name": "Namn",
"dialog.create_texture.folder": "Mapp",
"dialog.create_texture.template": "Mall",
"dialog.create_texture.resolution": "Upplösning",
@@ -591,7 +589,6 @@
"display.scale": "Skala",
"display.slot": "Plats",
"display.reference": "Referensmodell",
- "display.presetname": "Namn",
"display.reference.player": "Spelare",
"display.reference.zombie": "Zombie",
"display.reference.armor_stand": "Rustningsställ",
@@ -791,9 +788,7 @@
"action.open_backup_folder": "Öppna säkerhetskopieringsmappen",
"action.open_backup_folder.desc": "Öppnar säkerhetskopieringsmappen för Blockbench",
"switches.mirror": "Spegla UV",
- "Name of the Language you are editing, NOT English": {
- "language_name": "Svenska"
- },
+ "language_name": "Svenska",
"message.plugin_reload": "Uppdaterade %0 lokala plugins",
"settings.brightness": "Ljusstyrka",
"settings.brightness.desc": "Förhandsgranskningens ljusstyrka. Standard är 50",
@@ -912,7 +907,6 @@
"message.wireframe.disabled": "Wireframe view disabled",
"dialog.project.box_uv": "Box UV",
"dialog.convert_project.title": "Convert Project",
- "dialog.convert_project.format": "Format",
"dialog.convert_project.text": "Are you sure you want to convert this project? You cannot undo this step.",
"dialog.create_texture.double_use": "Keep Multi Texture Occupancy",
"dialog.model_stats.title": "Model Stats",
@@ -966,5 +960,28 @@
"panel.element.origin": "Pivot Point",
"panel.element.rotation": "Rotation",
"message.canvas_limit_error.title": "Canvas Limit Error",
- "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the origin to prevent this."
+ "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the pivot point to prevent this.",
+ "data.effect": "Effect",
+ "generic.name": "Name",
+ "settings.recent_projects": "Recent Model Cap",
+ "settings.recent_projects.desc": "Maximum number of recent models to remember",
+ "settings.volume": "Volume",
+ "settings.volume.desc": "Volume control for sound effects in animations",
+ "action.change_keyframe_file": "Select File",
+ "action.change_keyframe_file.desc": "Select an audio file to preview a sound effect.",
+ "action.clear_timeline": "Clear Timeline",
+ "action.clear_timeline.desc": "Clear all unselected bones from the timeline",
+ "action.select_effect_animator": "Animate Effects",
+ "action.select_effect_animator.desc": "Opens timeline to add sound and particle effects",
+ "action.timeline_focus": "Channel",
+ "action.timeline_focus.desc": "Select the animation channels to display in the timeline",
+ "action.timeline_focus.all": "All",
+ "timeline.particle": "Particle",
+ "timeline.sound": "Sound",
+ "timeline.effects": "Effects",
+ "data.format": "Format",
+ "format.optifine_part": "OptiFine Part",
+ "format.optifine_part.desc": "JPM part for OptiFine entity models",
+ "action.reverse_keyframes": "Reverse Keyframes",
+ "action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
}
\ No newline at end of file
diff --git a/lang/zh.json b/lang/zh.json
index 8d28997f9..6fd8df566 100644
--- a/lang/zh.json
+++ b/lang/zh.json
@@ -7,7 +7,7 @@
"dialog.save": "保存",
"dialog.discard": "弃置",
"dialog.dontshowagain": "不再显示",
- "data.cube": "Cube",
+ "data.cube": "立方体",
"data.group": "组",
"data.texture": "材质",
"data.plugin": "插件",
@@ -47,7 +47,7 @@
"keys.printscreen": "截屏键",
"keys.pause": "暂停键",
"message.rotation_limit.title": "旋转限制",
- "message.rotation_limit.message": "受 Minecarft 的限制,仅允许旋转单个轴,且旋转角度限制为22.5的倍数。在不同的轴上旋转将清除其他轴上的旋转。如果您的建模需要自由旋转,请禁用“限制旋转”选项",
+ "message.rotation_limit.message": "受 Minecarft 的限制,仅允许旋转单个轴,且旋转角度限制为22.5的倍数。在不同的轴上旋转将清除其他轴上的旋转。如果您的模型需要自由旋转,请禁用“限制旋转”选项",
"message.file_not_found.title": "未找到文件",
"message.file_not_found.message": "Blockbench 找不到该文件。请确保此文件为本地文件而不是云文件。",
"message.screenshot.title": "屏幕截图",
@@ -78,8 +78,8 @@
"message.close_warning.message": "是否保存模型?",
"message.close_warning.web": "您的项目将不会被保存。确定要退出吗?",
"message.default_textures.title": "默认贴图",
- "message.default_textures.message": "选择默认的资源包 \"textures\" 文件夹",
- "message.default_textures.detail": "从 Minecraft jar里提取或在网上下载默认资源包,然后找到 \"textures\" 文件夹并打开。Blockbench 将会在当前资源包找不到对应贴图时从此位置获取贴图。",
+ "message.default_textures.message": "选择默认资源包的 \"textures\" 文件夹",
+ "message.default_textures.detail": "从 Minecraft jar 里提取或在网上下载默认资源包,然后找到 \"textures\" 文件夹并打开。Blockbench 将会在当前资源包找不到对应贴图时从此位置获取贴图。",
"message.default_textures.select": "选择默认的 \"textures\" 文件夹",
"message.image_editor.title": "选择图像编辑器",
"message.image_editor.file": "选择文件...",
@@ -93,7 +93,7 @@
"message.load_plugin_app": "是否允许此插件更改您的PC?请仅加载您信任的插件。",
"message.load_plugin_web": "您想加载这个插件吗?请仅加载您信任的插件。",
"message.preset_no_info": "预设不包含此槽位的信息",
- "message.restart_to_update": "重新启动 Blockbench 以应用更改",
+ "message.restart_to_update": "重启 Blockbench 以应用更改",
"message.save_file": "保存为 %0",
"message.save_obj": "保存为 .obj 模型",
"message.rename_cubes": "重命名立方体",
@@ -103,10 +103,9 @@
"dialog.project.geoname": "生物几何体名称",
"dialog.project.openparent": "打开父类",
"dialog.project.ao": "环境光遮蔽",
- "dialog.project.width": "宽度",
- "dialog.project.height": "高度",
+ "dialog.project.width": "贴图宽度",
+ "dialog.project.height": "贴图高度",
"dialog.texture.title": "贴图",
- "dialog.texture.name": "名字",
"dialog.texture.variable": "变量",
"dialog.texture.namespace": "命名空间",
"dialog.texture.folder": "文件夹",
@@ -137,7 +136,7 @@
"dialog.plugins.available": "可用",
"dialog.plugins.install": "安装",
"dialog.plugins.uninstall": "卸载",
- "dialog.plugins.reload": "刷新",
+ "dialog.plugins.reload": "重载",
"dialog.plugins.none_installed": "未安装插件",
"dialog.plugins.none_available": "没有可用的插件",
"dialog.plugins.outdated": "需要更高版本的 Blockbench",
@@ -150,7 +149,6 @@
"dialog.entitylist.bones": "骨骼",
"dialog.entitylist.cubes": "立方体",
"dialog.create_texture.title": "创建贴图",
- "dialog.create_texture.name": "名字",
"dialog.create_texture.folder": "文件夹",
"dialog.create_texture.template": "模板",
"dialog.create_texture.resolution": "分辨率",
@@ -206,7 +204,7 @@
"settings.category.dialogs": "对话框",
"settings.category.export": "导出",
"settings.language": "语言",
- "settings.language.desc": "界面语言,重新启动 Blockbench 以应用更改.",
+ "settings.language.desc": "界面语言,重启 Blockbench 以应用更改。",
"settings.show_actions": "显示操作",
"settings.show_actions.desc": "在状态栏中显示所有操作",
"settings.backup_interval": "备份间隔",
@@ -362,7 +360,7 @@
"action.export_optifine_full": "导出 OptiFine JEM",
"action.export_optifine_full.desc": "导出 OptiFine 实体文件(Json Entity Model)",
"action.export_obj": "导出 OBJ 模型",
- "action.export_obj.desc": "导出 Wavefront OBJ 模型以用于其他程序或上传到 Sketchfab",
+ "action.export_obj.desc": "导出 Wavefront OBJ 模型以用于渲染或游戏引擎",
"action.settings_window": "设置...",
"action.settings_window.desc": "打开 Blockbench 设置对话框。",
"action.plugins_window": "插件",
@@ -379,7 +377,7 @@
"action.reset_layout.desc": "将布局重置为 Blockbench 的默认值",
"action.load_plugin": "从文件加载插件",
"action.load_plugin.desc": "通过导入源文件加载插件。",
- "action.reload_plugins": "重新加载插件",
+ "action.reload_plugins": "重载插件",
"action.reload_plugins.desc": "重载所有开发插件。",
"action.uv_dialog": "打开全部 UV 窗口",
"action.uv_dialog.desc": "打开 UV 对话框以查看彼此相邻的所有面",
@@ -390,17 +388,17 @@
"action.redo": "重做",
"action.redo.desc": "重做上次撤消",
"action.copy": "复制",
- "action.copy.desc": "复制选定的,面或显示设置.",
+ "action.copy.desc": "复制选定的内容、面或显示设置",
"action.paste": "粘贴",
- "action.paste.desc": "粘贴选定的,面或显示设置",
+ "action.paste.desc": "粘贴选定的内容、面或显示设置",
"action.cut": "剪切",
- "action.cut.desc": "剪切选定的,面或显示设置",
+ "action.cut.desc": "剪切选定的内容、面或显示设置",
"action.add_cube": "添加立方体",
"action.add_cube.desc": "添加一个新的立方体",
"action.add_group": "添加组",
"action.add_group.desc": "添加一个新的组",
"action.outliner_toggle": "切换更多选项",
- "action.outliner_toggle.desc": "切换开关以在outliner中添加更多选项",
+ "action.outliner_toggle.desc": "切换开关以在大纲中添加更多选项",
"action.duplicate": "生成副本",
"action.duplicate.desc": "复制选定的立方体或组",
"action.delete": "删除",
@@ -502,7 +500,7 @@
"menu.file.import": "导入",
"menu.file.export": "导出",
"menu.transform.rotate": "旋转",
- "menu.transform.flip": "翻动",
+ "menu.transform.flip": "翻转",
"menu.transform.center": "中心",
"menu.transform.properties": "属性",
"menu.display.preset": "应用预设",
@@ -582,7 +580,7 @@
"display.slot.third_left": "第三人称左手",
"display.slot.first_right": "第一人称右手",
"display.slot.first_left": "第一人称左手",
- "display.slot.head": "头上",
+ "display.slot.head": "头顶",
"display.slot.ground": "地面",
"display.slot.frame": "展示框",
"display.slot.gui": "GUI",
@@ -591,7 +589,6 @@
"display.scale": "缩放",
"display.slot": "位置",
"display.reference": "参考模型",
- "display.presetname": "名称",
"display.reference.player": "玩家",
"display.reference.zombie": "僵尸",
"display.reference.armor_stand": "盔甲架",
@@ -601,7 +598,7 @@
"display.reference.bow": "弓",
"display.reference.block": "方块",
"display.reference.frame": "物品展示框",
- "display.reference.inventory_nine": "合成台",
+ "display.reference.inventory_nine": "3x3",
"display.reference.inventory_full": "物品栏",
"display.reference.hud": "HUD",
"display.preset.blank_name": "请输入名称",
@@ -625,7 +622,7 @@
"action.open_model_folder.desc": "打开包含模型的文件夹",
"action.change_textures_folder": "更改材质纹理位置",
"action.change_textures_folder.desc": "更改保存所有纹理的文件夹",
- "menu.texture.particle": "使用的颗粒",
+ "menu.texture.particle": "作为颗粒贴图",
"message.update_notification.title": "有可用更新",
"message.update_notification.message": "新的 Blockbench 版本“%0”可用。你想现在安装吗?",
"message.update_notification.install": "安装",
@@ -637,7 +634,7 @@
"dialog.shift_uv.horizontal": "横向",
"dialog.shift_uv.vertical": "垂直",
"keybindings.reset": "重启",
- "keybindings.clear": "Empty",
+ "keybindings.clear": "清空",
"action.cube_counter": "立方体数量",
"action.uv_rotation": "UV旋转",
"action.uv_rotation.desc": "UV面的旋转",
@@ -670,13 +667,13 @@
"menu.toolbar.edit": "自定义",
"menu.toolbar.reset": "重置",
"uv_editor.rotated": "旋转",
- "uv_editor.auto_cull": "自己选择面",
+ "uv_editor.auto_cull": "剔除面",
"uv_editor.copied": "复制面",
"uv_editor.pasted": "粘贴面",
"uv_editor.copied_x": "复制 %0 面",
"uv_editor.reset": "重设面",
"uv_editor.maximized": "最大化",
- "uv_editor.autouv": "尺寸自动",
+ "uv_editor.autouv": "自动尺寸",
"uv_editor.mirrored": "镜像",
"uv_editor.to_all": "已适用于所有面",
"uv_editor.transparent": "设置透明度",
@@ -719,8 +716,8 @@
"action.slider_keyframe_time": "时间码",
"action.slider_keyframe_time.desc": "修改选定关键帧的时间码",
"timeline.rotation": "旋转",
- "timeline.position": "定位",
- "timeline.scale": "放大",
+ "timeline.position": "位置",
+ "timeline.scale": "缩放",
"menu.timeline.add": "添加关键帧",
"menu.keyframe.quaternion": "四元数",
"panel.animations": "动画",
@@ -730,8 +727,8 @@
"generic.rename": "重命名",
"message.rename_animation": "重命名动画",
"message.animation_update_var": "动画更新变量",
- "message.no_animation_selected": "你必须选择有动画才能执行此操作",
- "message.no_bone_selected": "你必须选择一个骨骼才能执行此操作",
+ "message.no_animation_selected": "你必须选中一个动画才能执行此操作",
+ "message.no_bone_selected": "你必须选中一个骨骼才能执行此操作",
"message.duplicate_groups.title": "骨骼名称复制",
"message.duplicate_groups.message": "此骨骼的名称存在于多个骨骼上,这可能会导致问题.\n重复命名错误",
"action.select_all_keyframes": "选择所有关键帧",
@@ -739,13 +736,13 @@
"action.delete_keyframes": "删除关键帧",
"action.delete_keyframes.desc": "删除所有选定的关键帧",
"menu.animation": "动画",
- "menu.animation.loop": "环",
+ "menu.animation.loop": "循环",
"menu.animation.override": "覆盖",
"menu.animation.anim_time_update": "更新变量",
"message.display_skin_model.title": "皮肤模型",
"message.display_skin_model.message": "选择皮肤模型的类型",
- "message.display_skin_model.classic": "经典\nSteve",
- "message.display_skin_model.slim": "瘦小\nAlex",
+ "message.display_skin_model.classic": "经典(Steve)",
+ "message.display_skin_model.slim": "纤细(Alex)",
"message.bone_material": "改变骨骼材料",
"action.slider_animation_length": "动画长度",
"action.slider_animation_length.desc": "更改所选的动画长度",
@@ -755,31 +752,31 @@
"panel.variable_placeholders": "变量占位符",
"panel.variable_placeholders.info": "列出要通过 name=value 预览的变量",
"status_bar.vertex_distance": "距离: %0",
- "dialog.create_gif.title": "录制GIF",
- "dialog.create_gif.length": "长度(秒)",
+ "dialog.create_gif.title": "录制 GIF",
+ "dialog.create_gif.length": "长度(秒)",
"dialog.create_gif.fps": "FPS",
"dialog.create_gif.compression": "压缩量",
"dialog.create_gif.play": "播放动画",
"category.animation": "动画",
- "action.record_model_gif": "录制GIF",
+ "action.record_model_gif": "录制 GIF",
"action.record_model_gif.desc": "从当前角度记录模型的GIF动画",
"display.mirror": "镜像",
"data.separator": "分隔符",
"message.set_background_position.title": "背景位置",
"menu.preview.background.set_position": "设置位置",
"dialog.toolbar_edit.hidden": "隐藏",
- "action.export_class_entity": "导出为java实体模型",
- "action.export_class_entity.desc": "作为java class导出模型",
+ "action.export_class_entity": "导出为 Java 实体",
+ "action.export_class_entity.desc": "导出为 Java 类",
"settings.seethrough_outline": "X-Ray 轮廓",
- "settings.seethrough_outline.desc": "通过对象显示轮廓",
+ "settings.seethrough_outline.desc": "显示物体被遮挡的轮廓",
"mode.edit": "编辑模式",
"mode.paint": "画板模式",
"mode.display": "显示调整模式",
"mode.animate": "动画模式",
- "status_bar.recording_gif": "录制GIF",
- "status_bar.processing_gif": "处理GIF",
+ "status_bar.recording_gif": "正在录制 GIF",
+ "status_bar.processing_gif": "正在处理 GIF",
"settings.backup_retain": "备份保留期限",
- "settings.backup_retain.desc": "设置Blockbench在几天内保留旧备份的时间",
+ "settings.backup_retain.desc": "设置 Blockbench 在几天内保留旧备份的时间",
"action.rotate_tool": "旋转",
"action.rotate_tool.desc": "用于选择和旋转立方体的工具",
"action.fill_tool": "油漆桶",
@@ -789,12 +786,10 @@
"action.color_picker": "颜色选择器",
"action.color_picker.desc": "用于选择材质纹理上像素颜色的工具",
"action.open_backup_folder": "打开备份文件夹",
- "action.open_backup_folder.desc": "打开Blockbench备份文件夹",
+ "action.open_backup_folder.desc": "打开 Blockbench 备份文件夹",
"switches.mirror": "镜像 UV",
- "Name of the Language you are editing, NOT English": {
- "language_name": "简体中文(中国)"
- },
- "message.plugin_reload": "重新加载 %0 个本地插件",
+ "language_name": "简体中文",
+ "message.plugin_reload": "已重载 %0 个本地插件",
"settings.brightness": "亮度",
"settings.brightness.desc": "预览的亮度,默认值为50",
"menu.preview.perspective.reset": "重置相机位置",
@@ -803,9 +798,9 @@
"action.fill_mode.face": "面",
"action.fill_mode.color": "颜色",
"action.fill_mode.cube": "立方体",
- "action.toggle_mirror_uv": "镜面 UV",
+ "action.toggle_mirror_uv": "镜像 UV",
"action.toggle_mirror_uv.desc": "在所选立方体的X轴上切换UV镜像",
- "action.toggle_uv_overlay": "切换UV覆盖",
+ "action.toggle_uv_overlay": "切换 UV 覆盖",
"action.toggle_uv_overlay.desc": "启用后,将在纹理上方显示所有UV贴图叠加层",
"menu.texture.blank": "适用于无纹理面",
"dialog.scale.select_overflow": "选择溢出",
@@ -826,23 +821,23 @@
"message.outdated_client.message": "请更新到Blockbench的最新版本来执行此操作",
"action.export_asset_archive": "下载存档",
"action.export_asset_archive.desc": "下载包含模型及其中所有纹理的存档",
- "action.upload_sketchfab": "Sketchfab上传",
- "message.sketchfab.name_or_token": "请输入你的Sketchfab 密码和名称",
- "dialog.sketchfab_uploader.title": "上传Sketchfab模型",
+ "action.upload_sketchfab": "Sketchfab 上传",
+ "message.sketchfab.name_or_token": "请输入你的 Sketchfab 密码和名称",
+ "dialog.sketchfab_uploader.title": "上传 Sketchfab 模型",
"dialog.sketchfab_uploader.token": "API Token",
- "dialog.sketchfab_uploader.about_token": "Token用于将Blockbench连接到Sketchfab帐户,您可以在 %0 上找到它",
+ "dialog.sketchfab_uploader.about_token": "Token 用于将 Blockbench 连接到 Sketchfab 帐户,您可以在 %0 上找到它",
"dialog.sketchfab_uploader.name": "模型名称",
"dialog.sketchfab_uploader.description": "描述",
"dialog.sketchfab_uploader.tags": "标签",
"settings.sketchfab_token": "Sketchfab Token",
- "settings.sketchfab_token.desc": "用于授权Blockbench上传到Sketchfab帐户的Token",
+ "settings.sketchfab_token.desc": "用于授权 Blockbench 上传到 Sketchfab 帐户的 Token",
"panel.color": "颜色",
"data.origin": "原点",
"message.sketchfab.success": "上传的模型成功",
- "message.sketchfab.error": "无法上传模型到Sketchfab",
+ "message.sketchfab.error": "无法上传模型到 Sketchfab",
"settings.outliner_colors": "大纲颜色",
"settings.outliner_colors.desc": "在大纲中显示立方体的颜色",
- "action.upload_sketchfab.desc": "将你的模型上传到Sketchfab",
+ "action.upload_sketchfab.desc": "将你的模型上传到 Sketchfab",
"action.element_colors": "立方体颜色",
"action.element_colors.desc": "在大纲中显示立方体的颜色",
"texture.error.file": "文件未找到",
@@ -852,7 +847,7 @@
"message.recover_backup.title": "恢复模型",
"message.recover_backup.message": "Blockblench 被异常关闭,是否恢复正在编辑的模型?",
"message.install_plugin": "安装插件中 %0",
- "message.invalid_session.title": "无效的会话Token",
+ "message.invalid_session.title": "无效的会话 Token",
"message.invalid_session.message": "你尝试加入的会话已过期或提供的Token无效",
"dialog.create_texture.power": "2的幂的大小",
"dialog.create_gif.turn": "旋转台速度",
@@ -863,7 +858,7 @@
"dialog.edit_session.title": "编辑会话",
"edit_session.username": "用户名",
"edit_session.token": "密码",
- "edit_session.about": "编辑会话可用于在互联网上进行模型制作协作,创建一个会话并复制该令牌并将其发送给朋友,然后朋友可以使用它来加入模型制作协作.",
+ "edit_session.about": "编辑会话可用于在互联网上进行模型制作协作,创建一个会话并复制该令牌并将其发送给朋友,然后朋友可以使用它来加入模型制作协作。",
"edit_session.join": "加入会话",
"edit_session.create": "创建会话",
"edit_session.quit": "退出会话",
@@ -896,13 +891,13 @@
"format.free": "自由模型",
"format.free.desc": "制作 Unity 等游戏制作软件的无建模限制的模型",
"format.java_block": "Java 方块/物品",
- "format.java_block.desc": "Java 版方块模型,大小和旋转都有限制",
+ "format.java_block.desc": "Java 版方块模型,大小和旋转都有限制",
"format.bedrock": "基岩版模型",
"format.bedrock.desc": "适用于基岩版的模型",
"format.bedrock_old": "旧版基岩版模型",
"format.bedrock_old.desc": "Pre-1.12 基岩版实体模型",
"format.modded_entity": "模组版实体",
- "format.modded_entity.desc": "为模组所适用的模型,能够直接以 .java 的class文件形式导出",
+ "format.modded_entity.desc": "为模组所适用的模型,能够直接以 .java 的类文件形式导出",
"format.optifine_entity": "OptiFine 实体",
"format.optifine_entity.desc": "OptiFine 自定义的实体模型",
"keys.mouse": "鼠标按键 %0",
@@ -912,7 +907,6 @@
"message.wireframe.disabled": "线框视图已禁用",
"dialog.project.box_uv": "盒子UV",
"dialog.convert_project.title": "转换工程",
- "dialog.convert_project.format": "格式",
"dialog.convert_project.text": "你确定想要转换此工程么?此步骤无法撤销。",
"dialog.create_texture.double_use": "保持多纹理占用",
"dialog.model_stats.title": "模型统计",
@@ -963,8 +957,31 @@
"panel.element": "元素",
"panel.element.position": "位置",
"panel.element.size": "尺寸",
- "panel.element.origin": "旋转源点",
+ "panel.element.origin": "旋转原点",
"panel.element.rotation": "旋转",
- "message.canvas_limit_error.title": "Canvas Limit Error",
- "message.canvas_limit_error.message": "The action could not be performed correctly because the format limits the canvas to 48 units. Shift the origin to prevent this."
+ "message.canvas_limit_error.title": "画布限制错误",
+ "message.canvas_limit_error.message": "无法正确执行此操作,因为此格式将画布限制为48个单位.移动原点以防止这种情况发生.",
+ "data.effect": "Effect",
+ "generic.name": "Name",
+ "settings.recent_projects": "Recent Model Cap",
+ "settings.recent_projects.desc": "Maximum number of recent models to remember",
+ "settings.volume": "Volume",
+ "settings.volume.desc": "Volume control for sound effects in animations",
+ "action.change_keyframe_file": "Select File",
+ "action.change_keyframe_file.desc": "Select an audio file to preview a sound effect.",
+ "action.clear_timeline": "Clear Timeline",
+ "action.clear_timeline.desc": "Clear all unselected bones from the timeline",
+ "action.select_effect_animator": "Animate Effects",
+ "action.select_effect_animator.desc": "Opens timeline to add sound and particle effects",
+ "action.timeline_focus": "Channel",
+ "action.timeline_focus.desc": "Select the animation channels to display in the timeline",
+ "action.timeline_focus.all": "All",
+ "timeline.particle": "Particle",
+ "timeline.sound": "Sound",
+ "timeline.effects": "Effects",
+ "data.format": "Format",
+ "format.optifine_part": "OptiFine Part",
+ "format.optifine_part.desc": "JPM part for OptiFine entity models",
+ "action.reverse_keyframes": "Reverse Keyframes",
+ "action.reverse_keyframes.desc": "Reverse the order of the selected keyframes"
}
\ No newline at end of file
diff --git a/lib/spectrum.js b/lib/spectrum.js
index ca1ed4e9e..1542d2964 100644
--- a/lib/spectrum.js
+++ b/lib/spectrum.js
@@ -4,2350 +4,2364 @@
// License: MIT
(function (factory) {
- "use strict";
-
- if (typeof define === 'function' && define.amd) { // AMD
- define(['jquery'], factory);
- }
- else if (typeof exports == "object" && typeof module == "object") { // CommonJS
- module.exports = factory(require('jquery'));
- }
- else { // Browser
- factory(jQuery);
- }
+ "use strict";
+
+ if (typeof define === 'function' && define.amd) { // AMD
+ define(['jquery'], factory);
+ }
+ else if (typeof exports == "object" && typeof module == "object") { // CommonJS
+ module.exports = factory(require('jquery'));
+ }
+ else { // Browser
+ factory(jQuery);
+ }
})(function($, undefined) {
- "use strict";
-
- var defaultOpts = {
-
- // Callbacks
- beforeShow: noop,
- move: noop,
- change: noop,
- show: noop,
- hide: noop,
-
- // Options
- color: false,
- flat: false,
- showInput: false,
- allowEmpty: false,
- showButtons: true,
- clickoutFiresChange: true,
- showInitial: false,
- showPalette: false,
- showPaletteOnly: false,
- hideAfterPaletteSelect: false,
- togglePaletteOnly: false,
- showSelectionPalette: true,
- localStorageKey: false,
- appendTo: "body",
- maxSelectionSize: 7,
- cancelText: '
clear',
- chooseText: '
check',
- togglePaletteMoreText: "more",
- togglePaletteLessText: "less",
- clearText: "Clear Color Selection",
- noColorSelectedText: "No Color Selected",
- preferredFormat: false,
- className: "", // Deprecated - use containerClassName and replacerClassName instead.
- containerClassName: "",
- replacerClassName: "",
- showAlpha: false,
- theme: "sp-light",
- palette: [["#ffffff", "#000000", "#ff0000", "#ff8000", "#ffff00", "#008000", "#0000ff", "#4b0082", "#9400d3"]],
- selectionPalette: [],
- disabled: false,
- offset: null
- },
- spectrums = [],
- IE = !!/msie/i.exec( window.navigator.userAgent ),
- rgbaSupport = (function() {
- function contains( str, substr ) {
- return !!~('' + str).indexOf(substr);
- }
-
- var elem = document.createElement('div');
- var style = elem.style;
- style.cssText = 'background-color:rgba(0,0,0,.5)';
- return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla');
- })(),
- replaceInput = [
- "
"
- ].join(''),
- markup = (function () {
-
- // IE does not support gradients with multiple stops, so we need to simulate
- // that for the rainbow slider with 8 divs that each have a single gradient
- var gradientFix = "";
- if (IE) {
- for (var i = 1; i <= 6; i++) {
- gradientFix += "
";
- }
- }
-
- return [
- "
",
- "
",
- "
",
- "
",
- "",
- "
",
- "
",
- "
",
- "
",
- "
",
- "
",
- "
",
- "
",
- "
",
- "
",
- "
",
- gradientFix,
- "
",
- "
",
- "
",
- "
",
- "
",
- "",
- "
",
- "
",
- "
",
- "
",
- "
"
- ].join("");
- })();
-
- function paletteTemplate (p, color, className, opts) {
- var html = [];
- for (var i = 0; i < p.length; i++) {
- var current = p[i];
- if(current) {
- var tiny = tinycolor(current);
- var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light";
- c += (tinycolor.equals(color, current)) ? " sp-thumb-active" : "";
- var formattedString = tiny.toString(opts.preferredFormat || "rgb");
- var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter();
- html.push('
');
- } else {
- var cls = 'sp-clear-display';
- html.push($('
')
- .append($('
')
- .attr('title', opts.noColorSelectedText)
- )
- .html()
- );
- }
- }
- return "
" + html.join('') + "
";
- }
-
- function hideAll() {
- for (var i = 0; i < spectrums.length; i++) {
- if (spectrums[i]) {
- spectrums[i].hide();
- }
- }
- }
-
- function instanceOptions(o, callbackContext) {
- var opts = $.extend({}, defaultOpts, o);
- opts.callbacks = {
- 'move': bind(opts.move, callbackContext),
- 'change': bind(opts.change, callbackContext),
- 'show': bind(opts.show, callbackContext),
- 'hide': bind(opts.hide, callbackContext),
- 'beforeShow': bind(opts.beforeShow, callbackContext)
- };
-
- return opts;
- }
-
- function spectrum(element, o) {
-
- var opts = instanceOptions(o, element),
- flat = opts.flat,
- showSelectionPalette = opts.showSelectionPalette,
- localStorageKey = opts.localStorageKey,
- theme = opts.theme,
- callbacks = opts.callbacks,
- resize = throttle(reflow, 10),
- visible = false,
- isDragging = false,
- dragWidth = 0,
- dragHeight = 0,
- dragHelperHeight = 0,
- slideHeight = 0,
- slideWidth = 0,
- alphaWidth = 0,
- alphaSlideHelperWidth = 0,
- slideHelperHeight = 0,
- currentHue = 0,
- currentSaturation = 0,
- currentValue = 0,
- currentAlpha = 1,
- palette = [],
- paletteArray = [],
- paletteLookup = {},
- selectionPalette = opts.selectionPalette.slice(0),
- maxSelectionSize = opts.maxSelectionSize,
- draggingClass = "sp-dragging",
- shiftMovementDirection = null;
-
- var doc = element.ownerDocument,
- body = doc.body,
- boundElement = $(element),
- disabled = false,
- container = $(markup, doc).addClass(theme),
- pickerContainer = container.find(".sp-picker-container"),
- dragger = container.find(".sp-color"),
- dragHelper = container.find(".sp-dragger"),
- slider = container.find(".sp-hue"),
- slideHelper = container.find(".sp-slider"),
- alphaSliderInner = container.find(".sp-alpha-inner"),
- alphaSlider = container.find(".sp-alpha"),
- alphaSlideHelper = container.find(".sp-alpha-handle"),
- textInput = container.find(".sp-input"),
- paletteContainer = container.find(".sp-palette"),
- initialColorContainer = container.find(".sp-initial"),
- cancelButton = container.find(".sp-cancel"),
- clearButton = container.find(".sp-clear"),
- chooseButton = container.find(".sp-choose"),
- toggleButton = container.find(".sp-palette-toggle"),
- isInput = boundElement.is("input"),
- isInputTypeColor = isInput && boundElement.attr("type") === "color" && inputTypeColorSupport(),
- shouldReplace = isInput && !flat,
- replacer = (shouldReplace) ? $(replaceInput).addClass(theme).addClass(opts.className).addClass(opts.replacerClassName) : $([]),
- offsetElement = (shouldReplace) ? replacer : boundElement,
- previewElement = replacer.find(".sp-preview-inner"),
- initialColor = opts.color || (isInput && boundElement.val()),
- colorOnShow = false,
- currentPreferredFormat = opts.preferredFormat,
- clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange,
- isEmpty = !initialColor,
- allowEmpty = opts.allowEmpty && !isInputTypeColor;
-
- function applyOptions() {
-
- if (opts.showPaletteOnly) {
- opts.showPalette = true;
- }
-
- toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText);
-
- if (opts.palette) {
- palette = opts.palette.slice(0);
- paletteArray = $.isArray(palette[0]) ? palette : [palette];
- paletteLookup = {};
- for (var i = 0; i < paletteArray.length; i++) {
- for (var j = 0; j < paletteArray[i].length; j++) {
- var rgb = tinycolor(paletteArray[i][j]).toRgbString();
- paletteLookup[rgb] = true;
- }
- }
- }
-
- container.toggleClass("sp-flat", flat);
- container.toggleClass("sp-input-disabled", !opts.showInput);
- container.toggleClass("sp-alpha-enabled", opts.showAlpha);
- container.toggleClass("sp-clear-enabled", allowEmpty);
- container.toggleClass("sp-buttons-disabled", !opts.showButtons);
- container.toggleClass("sp-palette-buttons-disabled", !opts.togglePaletteOnly);
- container.toggleClass("sp-palette-disabled", !opts.showPalette);
- container.toggleClass("sp-palette-only", opts.showPaletteOnly);
- container.toggleClass("sp-initial-disabled", !opts.showInitial);
- container.addClass(opts.className).addClass(opts.containerClassName);
-
- reflow();
- }
-
- function initialize() {
-
- if (IE) {
- container.find("*:not(input)").attr("unselectable", "on");
- }
-
- applyOptions();
-
- if (shouldReplace) {
- boundElement.after(replacer).hide();
- }
-
- if (!allowEmpty) {
- clearButton.hide();
- }
-
- if (flat) {
- boundElement.after(container).hide();
- }
- else {
-
- var appendTo = opts.appendTo === "parent" ? boundElement.parent() : $(opts.appendTo);
- if (appendTo.length !== 1) {
- appendTo = $("body");
- }
-
- appendTo.append(container);
- }
-
- updateSelectionPaletteFromStorage();
-
- offsetElement.bind("click.spectrum touchstart.spectrum", function (e) {
- if (!disabled) {
- toggle();
- }
-
- e.stopPropagation();
-
- if (!$(e.target).is("input")) {
- e.preventDefault();
- }
- });
-
- if(boundElement.is(":disabled") || (opts.disabled === true)) {
- disable();
- }
-
- // Prevent clicks from bubbling up to document. This would cause it to be hidden.
- container.click(stopPropagation);
-
- // Handle user typed input
- textInput.change(setFromTextInput);
- textInput.bind("paste", function () {
- setTimeout(setFromTextInput, 1);
- });
- textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } });
-
- cancelButton.html(opts.cancelText);
- cancelButton.bind("click.spectrum", function (e) {
- e.stopPropagation();
- e.preventDefault();
- revert();
- hide();
- });
-
- clearButton.attr("title", opts.clearText);
- clearButton.bind("click.spectrum", function (e) {
- e.stopPropagation();
- e.preventDefault();
- isEmpty = true;
- move();
-
- if(flat) {
- //for the flat style, this is a change event
- updateOriginalInput(true);
- }
- });
-
- chooseButton.html(opts.chooseText);
- chooseButton.bind("click.spectrum", function (e) {
- e.stopPropagation();
- e.preventDefault();
-
- if (IE && textInput.is(":focus")) {
- textInput.trigger('change');
- }
-
- if (isValid()) {
- updateOriginalInput(true);
- hide();
- }
- });
-
- toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText);
- toggleButton.bind("click.spectrum", function (e) {
- e.stopPropagation();
- e.preventDefault();
-
- opts.showPaletteOnly = !opts.showPaletteOnly;
-
- // To make sure the Picker area is drawn on the right, next to the
- // Palette area (and not below the palette), first move the Palette
- // to the left to make space for the picker, plus 5px extra.
- // The 'applyOptions' function puts the whole container back into place
- // and takes care of the button-text and the sp-palette-only CSS class.
- if (!opts.showPaletteOnly && !flat) {
- container.css('left', '-=' + (pickerContainer.outerWidth(true) + 5));
- }
- applyOptions();
- });
-
- draggable(alphaSlider, function (dragX, dragY, e) {
- currentAlpha = (dragX / alphaWidth);
- isEmpty = false;
- if (e.shiftKey) {
- currentAlpha = Math.round(currentAlpha * 10) / 10;
- }
-
- move();
- }, dragStart, dragStop);
-
- draggable(slider, function (dragX, dragY) {
- currentHue = parseFloat(dragY / slideHeight);
- isEmpty = false;
- if (!opts.showAlpha) {
- currentAlpha = 1;
- }
- move();
- }, dragStart, dragStop);
-
- draggable(dragger, function (dragX, dragY, e) {
-
- // shift+drag should snap the movement to either the x or y axis.
- if (!e.shiftKey) {
- shiftMovementDirection = null;
- }
- else if (!shiftMovementDirection) {
- var oldDragX = currentSaturation * dragWidth;
- var oldDragY = dragHeight - (currentValue * dragHeight);
- var furtherFromX = Math.abs(dragX - oldDragX) > Math.abs(dragY - oldDragY);
-
- shiftMovementDirection = furtherFromX ? "x" : "y";
- }
-
- var setSaturation = !shiftMovementDirection || shiftMovementDirection === "x";
- var setValue = !shiftMovementDirection || shiftMovementDirection === "y";
-
- if (setSaturation) {
- currentSaturation = parseFloat(dragX / dragWidth);
- }
- if (setValue) {
- currentValue = parseFloat((dragHeight - dragY) / dragHeight);
- }
-
- isEmpty = false;
- if (!opts.showAlpha) {
- currentAlpha = 1;
- }
-
- move();
-
- }, dragStart, dragStop);
-
- if (!!initialColor) {
- set(initialColor);
-
- // In case color was black - update the preview UI and set the format
- // since the set function will not run (default color is black).
- updateUI();
- currentPreferredFormat = opts.preferredFormat || tinycolor(initialColor).format;
-
- addColorToSelectionPalette(initialColor);
- }
- else {
- updateUI();
- }
-
- if (flat) {
- show();
- }
-
- function paletteElementClick(e) {
- if (e.data && e.data.ignore) {
- set($(e.target).closest(".sp-thumb-el").data("color"));
- move();
- }
- else {
- set($(e.target).closest(".sp-thumb-el").data("color"));
- move();
- updateOriginalInput(true);
- if (opts.hideAfterPaletteSelect) {
- hide();
- }
- }
-
- return false;
- }
-
- var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum";
- paletteContainer.delegate(".sp-thumb-el", paletteEvent, paletteElementClick);
- initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, paletteElementClick);
- }
-
- function updateSelectionPaletteFromStorage() {
-
- if (localStorageKey && window.localStorage) {
-
- // Migrate old palettes over to new format. May want to remove this eventually.
- try {
- var oldPalette = window.localStorage[localStorageKey].split(",#");
- if (oldPalette.length > 1) {
- delete window.localStorage[localStorageKey];
- $.each(oldPalette, function(i, c) {
- addColorToSelectionPalette(c);
- });
- }
- }
- catch(e) { }
-
- try {
- selectionPalette = window.localStorage[localStorageKey].split(";");
- }
- catch (e) { }
- }
- }
-
- function addColorToSelectionPalette(color) {
-
- if (showSelectionPalette) {
- var rgb = tinycolor(color).toRgbString();
- if (!paletteLookup[rgb] && $.inArray(rgb, selectionPalette) === -1) {
- selectionPalette.push(rgb);
- while(selectionPalette.length > maxSelectionSize) {
- selectionPalette.shift();
- }
- }
-
- if (localStorageKey && window.localStorage) {
- try {
- window.localStorage[localStorageKey] = selectionPalette.join(";");
- }
- catch(e) { }
- }
- }
- }
-
- function getUniqueSelectionPalette() {
- var unique = [];
- if (opts.showPalette) {
- for (var i = 0; i < selectionPalette.length; i++) {
- var rgb = tinycolor(selectionPalette[i]).toRgbString();
-
- if (!paletteLookup[rgb]) {
- unique.push(selectionPalette[i]);
- }
- }
- }
-
- return unique.reverse().slice(0, opts.maxSelectionSize);
- }
-
- function drawPalette() {
-
- var currentColor = get();
-
- var html = $.map(paletteArray, function (palette, i) {
- return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i, opts);
- });
-
- updateSelectionPaletteFromStorage();
-
- if (selectionPalette) {
- html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection", opts));
- }
-
- paletteContainer.html(html.join(""));
- }
-
- function drawInitial() {
- if (opts.showInitial) {
- var initial = colorOnShow;
- var current = get();
- initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial", opts));
- }
- }
-
- function dragStart() {
- if (dragHeight <= 0 || dragWidth <= 0 || slideHeight <= 0 || flat) {
- reflow();
- }
- isDragging = true;
- container.addClass(draggingClass);
- shiftMovementDirection = null;
- boundElement.trigger('dragstart.spectrum', [ get() ]);
- }
-
- function dragStop() {
- isDragging = false;
- container.removeClass(draggingClass);
- boundElement.trigger('dragstop.spectrum', [ get() ]);
- }
-
- function setFromTextInput() {
-
- var value = textInput.val();
-
- if ((value === null || value === "") && allowEmpty) {
- set(null);
- updateOriginalInput(true);
- }
- else {
- var tiny = tinycolor(value);
- if (tiny.isValid()) {
- set(tiny);
- updateOriginalInput(true);
- }
- else {
- textInput.addClass("sp-validation-error");
- }
- }
- }
-
- function toggle() {
- if (visible) {
- hide();
- }
- else {
- show();
- }
- }
-
- function show() {
- var event = $.Event('beforeShow.spectrum');
-
- if (visible) {
- reflow();
- return;
- }
-
- boundElement.trigger(event, [ get() ]);
-
- if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) {
- return;
- }
-
- hideAll();
- visible = true;
-
- $(doc).bind("keydown.spectrum", onkeydown);
- $(doc).bind("click.spectrum", clickout);
- $(window).bind("resize.spectrum", resize);
- replacer.addClass("sp-active");
- container.removeClass("sp-hidden");
-
- reflow();
- updateUI();
-
- colorOnShow = get();
-
- drawInitial();
- callbacks.show(colorOnShow);
- boundElement.trigger('show.spectrum', [ colorOnShow ]);
- }
-
- function onkeydown(e) {
- // Close on ESC
- if (e.keyCode === 27) {
- hide();
- }
- }
-
- function clickout(e) {
- // Return on right click.
- if (e.button == 2) { return; }
-
- // If a drag event was happening during the mouseup, don't hide
- // on click.
- if (isDragging) { return; }
-
- if (clickoutFiresChange) {
- updateOriginalInput(true);
- }
- else {
- revert();
- }
- hide();
- }
-
- function hide() {
- // Return if hiding is unnecessary
- if (!visible || flat) { return; }
- visible = false;
-
-
- if (isValid()) {
- updateOriginalInput(true);
- }
-
- $(doc).unbind("keydown.spectrum", onkeydown);
- $(doc).unbind("click.spectrum", clickout);
- $(window).unbind("resize.spectrum", resize);
-
- replacer.removeClass("sp-active");
- container.addClass("sp-hidden");
-
- callbacks.hide(get());
- boundElement.trigger('hide.spectrum', [ get() ]);
- }
-
- function cancel() {
- // Return if hiding is unnecessary
- if (!visible || flat) { return; }
- revert()
- visible = false;
-
- $(doc).unbind("keydown.spectrum", onkeydown);
- $(doc).unbind("click.spectrum", clickout);
- $(window).unbind("resize.spectrum", resize);
-
- replacer.removeClass("sp-active");
- container.addClass("sp-hidden");
-
- callbacks.hide(get());
- boundElement.trigger('hide.spectrum', [ get() ]);
- }
-
- function revert() {
- set(colorOnShow, true);
- }
-
- function set(color, ignoreFormatChange) {
- if (tinycolor.equals(color, get())) {
- // Update UI just in case a validation error needs
- // to be cleared.
- updateUI();
- return;
- }
-
- var newColor, newHsv;
- if (!color && allowEmpty) {
- isEmpty = true;
- } else {
- isEmpty = false;
- newColor = tinycolor(color);
- newHsv = newColor.toHsv();
-
- currentHue = (newHsv.h % 360) / 360;
- currentSaturation = newHsv.s;
- currentValue = newHsv.v;
- currentAlpha = newHsv.a;
- }
- updateUI();
-
- if (newColor && newColor.isValid() && !ignoreFormatChange) {
- currentPreferredFormat = opts.preferredFormat || newColor.getFormat();
- }
- }
-
- function get(opts) {
- opts = opts || { };
-
- if (allowEmpty && isEmpty) {
- return null;
- }
-
- return tinycolor.fromRatio({
- h: currentHue,
- s: currentSaturation,
- v: currentValue,
- a: Math.round(currentAlpha * 100) / 100
- }, { format: opts.format || currentPreferredFormat });
- }
-
- function isValid() {
- return !textInput.hasClass("sp-validation-error");
- }
-
- function move() {
- updateUI();
-
- callbacks.move(get());
- boundElement.trigger('move.spectrum', [ get() ]);
- }
-
- function updateUI() {
-
- textInput.removeClass("sp-validation-error");
-
- updateHelperLocations();
-
- // Update dragger background color (gradients take care of saturation and value).
- var flatColor = tinycolor.fromRatio({ h: currentHue, s: 1, v: 1 });
- dragger.css("background-color", flatColor.toHexString());
-
- // Get a format that alpha will be included in (hex and names ignore alpha)
- var format = currentPreferredFormat;
- if (currentAlpha < 1 && !(currentAlpha === 0 && format === "name")) {
- if (format === "hex" || format === "hex3" || format === "hex6" || format === "name") {
- format = "rgb";
- }
- }
-
- var realColor = get({ format: format }),
- displayColor = '';
-
- //reset background info for preview element
- previewElement.removeClass("sp-clear-display");
- previewElement.css('background-color', 'transparent');
-
- if (!realColor && allowEmpty) {
- // Update the replaced elements background with icon indicating no color selection
- previewElement.addClass("sp-clear-display");
- }
- else {
- var realHex = realColor.toHexString(),
- realRgb = realColor.toRgbString();
-
- // Update the replaced elements background color (with actual selected color)
- if (rgbaSupport || realColor.alpha === 1) {
- previewElement.css("background-color", realRgb);
- }
- else {
- previewElement.css("background-color", "transparent");
- previewElement.css("filter", realColor.toFilter());
- }
-
- if (opts.showAlpha) {
- var rgb = realColor.toRgb();
- rgb.a = 0;
- var realAlpha = tinycolor(rgb).toRgbString();
- var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")";
-
- if (IE) {
- alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({ gradientType: 1 }, realHex));
- }
- else {
- alphaSliderInner.css("background", "-webkit-" + gradient);
- alphaSliderInner.css("background", "-moz-" + gradient);
- alphaSliderInner.css("background", "-ms-" + gradient);
- // Use current syntax gradient on unprefixed property.
- alphaSliderInner.css("background",
- "linear-gradient(to right, " + realAlpha + ", " + realHex + ")");
- }
- }
-
- displayColor = realColor.toString(format);
- }
-
- // Update the text entry input as it changes happen
- if (opts.showInput) {
- textInput.val(displayColor);
- }
-
- if (opts.showPalette) {
- drawPalette();
- }
-
- drawInitial();
- }
-
- function updateHelperLocations() {
- var s = currentSaturation;
- var v = currentValue;
-
- if(allowEmpty && isEmpty) {
- //if selected color is empty, hide the helpers
- alphaSlideHelper.hide();
- slideHelper.hide();
- dragHelper.hide();
- }
- else {
- //make sure helpers are visible
- alphaSlideHelper.show();
- slideHelper.show();
- dragHelper.show();
-
- // Where to show the little circle in that displays your current selected color
- var dragX = s * dragWidth;
- var dragY = dragHeight - (v * dragHeight);
- dragX = Math.max(
- -dragHelperHeight,
- Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight)
- );
- dragY = Math.max(
- -dragHelperHeight,
- Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight)
- );
- dragHelper.css({
- "top": dragY + "px",
- "left": dragX + "px"
- });
-
- var alphaX = currentAlpha * alphaWidth;
- alphaSlideHelper.css({
- "left": (alphaX - (alphaSlideHelperWidth / 2)) + "px"
- });
-
- // Where to show the bar that displays your current selected hue
- var slideY = (currentHue) * slideHeight;
- slideHelper.css({
- "top": (slideY - slideHelperHeight) + "px"
- });
- }
- }
-
- function updateOriginalInput(fireCallback) {
- var color = get(),
- displayColor = '',
- hasChanged = !tinycolor.equals(color, colorOnShow);
-
- if (color) {
- displayColor = color.toString(currentPreferredFormat);
- // Update the selection palette with the current color
- addColorToSelectionPalette(color);
- }
-
- if (isInput) {
- boundElement.val(displayColor);
- }
-
- if (fireCallback && hasChanged) {
- callbacks.change(color);
- boundElement.trigger('change', [ color ]);
- }
- }
-
- function reflow() {
- if (!visible) {
- return; // Calculations would be useless and wouldn't be reliable anyways
- }
- dragWidth = dragger.width();
- dragHeight = dragger.height();
- dragHelperHeight = dragHelper.height();
- slideWidth = slider.width();
- slideHeight = slider.height();
- slideHelperHeight = slideHelper.height();
- alphaWidth = alphaSlider.width();
- alphaSlideHelperWidth = alphaSlideHelper.width();
-
- if (!flat) {
- container.css("position", "absolute");
- if (opts.offset) {
- container.offset(opts.offset);
- } else {
- container.offset(getOffset(container, offsetElement));
- }
- }
-
- updateHelperLocations();
-
- if (opts.showPalette) {
- drawPalette();
- }
-
- boundElement.trigger('reflow.spectrum');
- }
-
- function destroy() {
- boundElement.show();
- offsetElement.unbind("click.spectrum touchstart.spectrum");
- container.remove();
- replacer.remove();
- spectrums[spect.id] = null;
- }
-
- function option(optionName, optionValue) {
- if (optionName === undefined) {
- return $.extend({}, opts);
- }
- if (optionValue === undefined) {
- return opts[optionName];
- }
-
- opts[optionName] = optionValue;
-
- if (optionName === "preferredFormat") {
- currentPreferredFormat = opts.preferredFormat;
- }
- applyOptions();
- }
-
- function enable() {
- disabled = false;
- boundElement.attr("disabled", false);
- offsetElement.removeClass("sp-disabled");
- }
-
- function disable() {
- hide();
- disabled = true;
- boundElement.attr("disabled", true);
- offsetElement.addClass("sp-disabled");
- }
-
- function setOffset(coord) {
- opts.offset = coord;
- reflow();
- }
-
- initialize();
-
- var spect = {
- show: show,
- hide: hide,
- cancel: cancel,
- toggle: toggle,
- reflow: reflow,
- option: option,
- enable: enable,
- disable: disable,
- offset: setOffset,
- set: function (c) {
- set(c);
- updateOriginalInput();
- },
- get: get,
- destroy: destroy,
- container: container
- };
-
- spect.id = spectrums.push(spect) - 1;
-
- return spect;
- }
-
- /**
- * checkOffset - get the offset below/above and left/right element depending on screen position
- * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js
- */
- function getOffset(picker, input) {
- var extraY = 0;
- var dpWidth = picker.outerWidth();
- var dpHeight = picker.outerHeight();
- var inputHeight = input.outerHeight();
- var doc = picker[0].ownerDocument;
- var docElem = doc.documentElement;
- var viewWidth = docElem.clientWidth + $(doc).scrollLeft();
- var viewHeight = docElem.clientHeight + $(doc).scrollTop();
- var offset = input.offset();
- offset.top += inputHeight;
-
- offset.left -=
- Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
- Math.abs(offset.left + dpWidth - viewWidth) : 0);
-
- offset.top -=
- Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
- Math.abs(dpHeight + inputHeight - extraY) : extraY));
-
- return offset;
- }
-
- /**
- * noop - do nothing
- */
- function noop() {
-
- }
-
- /**
- * stopPropagation - makes the code only doing this a little easier to read in line
- */
- function stopPropagation(e) {
- e.stopPropagation();
- }
-
- /**
- * Create a function bound to a given object
- * Thanks to underscore.js
- */
- function bind(func, obj) {
- var slice = Array.prototype.slice;
- var args = slice.call(arguments, 2);
- return function () {
- return func.apply(obj, args.concat(slice.call(arguments)));
- };
- }
-
- /**
- * Lightweight drag helper. Handles containment within the element, so that
- * when dragging, the x is within [0,element.width] and y is within [0,element.height]
- */
- function draggable(element, onmove, onstart, onstop) {
- onmove = onmove || function () { };
- onstart = onstart || function () { };
- onstop = onstop || function () { };
- var doc = document;
- var dragging = false;
- var offset = {};
- var maxHeight = 0;
- var maxWidth = 0;
- var hasTouch = ('ontouchstart' in window);
-
- var duringDragEvents = {};
- duringDragEvents["selectstart"] = prevent;
- duringDragEvents["dragstart"] = prevent;
- duringDragEvents["touchmove mousemove"] = move;
- duringDragEvents["touchend mouseup"] = stop;
-
- function prevent(e) {
- if (e.stopPropagation) {
- e.stopPropagation();
- }
- if (e.preventDefault) {
- e.preventDefault();
- }
- e.returnValue = false;
- }
-
- function move(e) {
- if (dragging) {
- // Mouseup happened outside of window
- if (IE && doc.documentMode < 9 && !e.button) {
- return stop();
- }
-
- var t0 = e.originalEvent && e.originalEvent.touches && e.originalEvent.touches[0];
- var pageX = t0 && t0.pageX || e.pageX;
- var pageY = t0 && t0.pageY || e.pageY;
-
- var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth));
- var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight));
-
- if (hasTouch) {
- // Stop scrolling in iOS
- prevent(e);
- }
-
- onmove.apply(element, [dragX, dragY, e]);
- }
- }
-
- function start(e) {
- var rightclick = (e.which) ? (e.which == 3) : (e.button == 2);
-
- if (!rightclick && !dragging) {
- if (onstart.apply(element, arguments) !== false) {
- dragging = true;
- maxHeight = $(element).height();
- maxWidth = $(element).width();
- offset = $(element).offset();
-
- $(doc).bind(duringDragEvents);
- $(doc.body).addClass("sp-dragging");
-
- move(e);
-
- prevent(e);
- }
- }
- }
-
- function stop() {
- if (dragging) {
- $(doc).unbind(duringDragEvents);
- $(doc.body).removeClass("sp-dragging");
-
- // Wait a tick before notifying observers to allow the click event
- // to fire in Chrome.
- setTimeout(function() {
- onstop.apply(element, arguments);
- }, 0);
- }
- dragging = false;
- }
-
- $(element).bind("touchstart mousedown", start);
- }
-
- function throttle(func, wait, debounce) {
- var timeout;
- return function () {
- var context = this, args = arguments;
- var throttler = function () {
- timeout = null;
- func.apply(context, args);
- };
- if (debounce) clearTimeout(timeout);
- if (debounce || !timeout) timeout = setTimeout(throttler, wait);
- };
- }
-
- function inputTypeColorSupport() {
- return $.fn.spectrum.inputTypeColorSupport();
- }
-
- /**
- * Define a jQuery plugin
- */
- var dataID = "spectrum.id";
- $.fn.spectrum = function (opts, extra) {
-
- if (typeof opts == "string") {
-
- var returnValue = this;
- var args = Array.prototype.slice.call( arguments, 1 );
-
- this.each(function () {
- var spect = spectrums[$(this).data(dataID)];
- if (spect) {
- var method = spect[opts];
- if (!method) {
- throw new Error( "Spectrum: no such method: '" + opts + "'" );
- }
-
- if (opts == "get") {
- returnValue = spect.get();
- }
- else if (opts == "container") {
- returnValue = spect.container;
- }
- else if (opts == "option") {
- returnValue = spect.option.apply(spect, args);
- }
- else if (opts == "destroy") {
- spect.destroy();
- $(this).removeData(dataID);
- }
- else if (opts == "cancel") {
- spect.cancel();
- }
- else {
- method.apply(spect, args);
- }
- }
- });
-
- return returnValue;
- }
-
- // Initializing a new instance of spectrum
- return this.spectrum("destroy").each(function () {
- var options = $.extend({}, opts, $(this).data());
- var spect = spectrum(this, options);
- $(this).data(dataID, spect.id);
- });
- };
-
- $.fn.spectrum.load = true;
- $.fn.spectrum.loadOpts = {};
- $.fn.spectrum.draggable = draggable;
- $.fn.spectrum.defaults = defaultOpts;
- $.fn.spectrum.inputTypeColorSupport = function inputTypeColorSupport() {
- if (typeof inputTypeColorSupport._cachedResult === "undefined") {
- var colorInput = $("
")[0]; // if color element is supported, value will default to not null
- inputTypeColorSupport._cachedResult = colorInput.type === "color" && colorInput.value !== "";
- }
- return inputTypeColorSupport._cachedResult;
- };
-
- $.spectrum = { };
- $.spectrum.localization = { };
- $.spectrum.palettes = { };
-
- $.fn.spectrum.processNativeColorInputs = function () {
- var colorInputs = $("input[type=color]");
- if (colorInputs.length && !inputTypeColorSupport()) {
- colorInputs.spectrum({
- preferredFormat: "hex6"
- });
- }
- };
-
- // TinyColor v1.1.2
- // https://github.com/bgrins/TinyColor
- // Brian Grinstead, MIT License
-
- (function() {
-
- var trimLeft = /^[\s,#]+/,
- trimRight = /\s+$/,
- tinyCounter = 0,
- math = Math,
- mathRound = math.round,
- mathMin = math.min,
- mathMax = math.max,
- mathRandom = math.random;
-
- var tinycolor = function(color, opts) {
-
- color = (color) ? color : '';
- opts = opts || { };
-
- // If input is already a tinycolor, return itself
- if (color instanceof tinycolor) {
- return color;
- }
- // If we are called as a function, call using new instead
- if (!(this instanceof tinycolor)) {
- return new tinycolor(color, opts);
- }
-
- var rgb = inputToRGB(color);
- this._originalInput = color,
- this._r = rgb.r,
- this._g = rgb.g,
- this._b = rgb.b,
- this._a = rgb.a,
- this._roundA = mathRound(100*this._a) / 100,
- this._format = opts.format || rgb.format;
- this._gradientType = opts.gradientType;
-
- // Don't let the range of [0,255] come back in [0,1].
- // Potentially lose a little bit of precision here, but will fix issues where
- // .5 gets interpreted as half of the total, instead of half of 1
- // If it was supposed to be 128, this was already taken care of by `inputToRgb`
- if (this._r < 1) { this._r = mathRound(this._r); }
- if (this._g < 1) { this._g = mathRound(this._g); }
- if (this._b < 1) { this._b = mathRound(this._b); }
-
- this._ok = rgb.ok;
- this._tc_id = tinyCounter++;
- };
-
- tinycolor.prototype = {
- isDark: function() {
- return this.getBrightness() < 128;
- },
- isLight: function() {
- return !this.isDark();
- },
- isValid: function() {
- return this._ok;
- },
- getOriginalInput: function() {
- return this._originalInput;
- },
- getFormat: function() {
- return this._format;
- },
- getAlpha: function() {
- return this._a;
- },
- getBrightness: function() {
- var rgb = this.toRgb();
- return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
- },
- setAlpha: function(value) {
- this._a = boundAlpha(value);
- this._roundA = mathRound(100*this._a) / 100;
- return this;
- },
- toHsv: function() {
- var hsv = rgbToHsv(this._r, this._g, this._b);
- return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a };
- },
- toHsvString: function() {
- var hsv = rgbToHsv(this._r, this._g, this._b);
- var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100);
- return (this._a == 1) ?
- "hsv(" + h + ", " + s + "%, " + v + "%)" :
- "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")";
- },
- toHsl: function() {
- var hsl = rgbToHsl(this._r, this._g, this._b);
- return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a };
- },
- toHslString: function() {
- var hsl = rgbToHsl(this._r, this._g, this._b);
- var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100);
- return (this._a == 1) ?
- "hsl(" + h + ", " + s + "%, " + l + "%)" :
- "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")";
- },
- toHex: function(allow3Char) {
- return rgbToHex(this._r, this._g, this._b, allow3Char);
- },
- toHexString: function(allow3Char) {
- return '#' + this.toHex(allow3Char);
- },
- toHex8: function() {
- return rgbaToHex(this._r, this._g, this._b, this._a);
- },
- toHex8String: function() {
- return '#' + this.toHex8();
- },
- toInteger: function() {
- return Jimp.rgbaToInt(mathRound(this._r), mathRound(this._g), mathRound(this._b), this._a*255)
- },
- toRgb: function() {
- return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a };
- },
- toRgbString: function() {
- return (this._a == 1) ?
- "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" :
- "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")";
- },
- toPercentageRgb: function() {
- return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a };
- },
- toPercentageRgbString: function() {
- return (this._a == 1) ?
- "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" :
- "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")";
- },
- toName: function() {
- if (this._a === 0) {
- return "transparent";
- }
-
- if (this._a < 1) {
- return false;
- }
-
- return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false;
- },
- toFilter: function(secondColor) {
- var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a);
- var secondHex8String = hex8String;
- var gradientType = this._gradientType ? "GradientType = 1, " : "";
-
- if (secondColor) {
- var s = tinycolor(secondColor);
- secondHex8String = s.toHex8String();
- }
-
- return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")";
- },
- toString: function(format) {
- var formatSet = !!format;
- format = format || this._format;
-
- var formattedString = false;
- var hasAlpha = this._a < 1 && this._a >= 0;
- var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name");
-
- if (needsAlphaFormat) {
- // Special case for "transparent", all other non-alpha formats
- // will return rgba when there is transparency.
- if (format === "name" && this._a === 0) {
- return this.toName();
- }
- return this.toRgbString();
- }
- if (format === "rgb") {
- formattedString = this.toRgbString();
- }
- if (format === "prgb") {
- formattedString = this.toPercentageRgbString();
- }
- if (format === "hex" || format === "hex6") {
- formattedString = this.toHexString();
- }
- if (format === "hex3") {
- formattedString = this.toHexString(true);
- }
- if (format === "hex8") {
- formattedString = this.toHex8String();
- }
- if (format === "name") {
- formattedString = this.toName();
- }
- if (format === "hsl") {
- formattedString = this.toHslString();
- }
- if (format === "hsv") {
- formattedString = this.toHsvString();
- }
-
- return formattedString || this.toHexString();
- },
-
- _applyModification: function(fn, args) {
- var color = fn.apply(null, [this].concat([].slice.call(args)));
- this._r = color._r;
- this._g = color._g;
- this._b = color._b;
- this.setAlpha(color._a);
- return this;
- },
- lighten: function() {
- return this._applyModification(lighten, arguments);
- },
- brighten: function() {
- return this._applyModification(brighten, arguments);
- },
- darken: function() {
- return this._applyModification(darken, arguments);
- },
- desaturate: function() {
- return this._applyModification(desaturate, arguments);
- },
- saturate: function() {
- return this._applyModification(saturate, arguments);
- },
- greyscale: function() {
- return this._applyModification(greyscale, arguments);
- },
- spin: function() {
- return this._applyModification(spin, arguments);
- },
-
- _applyCombination: function(fn, args) {
- return fn.apply(null, [this].concat([].slice.call(args)));
- },
- analogous: function() {
- return this._applyCombination(analogous, arguments);
- },
- complement: function() {
- return this._applyCombination(complement, arguments);
- },
- monochromatic: function() {
- return this._applyCombination(monochromatic, arguments);
- },
- splitcomplement: function() {
- return this._applyCombination(splitcomplement, arguments);
- },
- triad: function() {
- return this._applyCombination(triad, arguments);
- },
- tetrad: function() {
- return this._applyCombination(tetrad, arguments);
- }
- };
-
- // If input is an object, force 1 into "1.0" to handle ratios properly
- // String input requires "1.0" as input, so 1 will be treated as 1
- tinycolor.fromRatio = function(color, opts) {
- if (typeof color == "object") {
- var newColor = {};
- for (var i in color) {
- if (color.hasOwnProperty(i)) {
- if (i === "a") {
- newColor[i] = color[i];
- }
- else {
- newColor[i] = convertToPercentage(color[i]);
- }
- }
- }
- color = newColor;
- }
-
- return tinycolor(color, opts);
- };
-
- // Given a string or object, convert that input to RGB
- // Possible string inputs:
- //
- // "red"
- // "#f00" or "f00"
- // "#ff0000" or "ff0000"
- // "#ff000000" or "ff000000"
- // "rgb 255 0 0" or "rgb (255, 0, 0)"
- // "rgb 1.0 0 0" or "rgb (1, 0, 0)"
- // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
- // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
- // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
- // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
- // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
- //
- function inputToRGB(color) {
-
- var rgb = { r: 0, g: 0, b: 0 };
- var a = 1;
- var ok = false;
- var format = false;
-
- if (typeof color == "string") {
- color = stringInputToObject(color);
- }
-
- if (typeof color == "object") {
- if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) {
- rgb = rgbToRgb(color.r, color.g, color.b);
- ok = true;
- format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb";
- }
- else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) {
- color.s = convertToPercentage(color.s);
- color.v = convertToPercentage(color.v);
- rgb = hsvToRgb(color.h, color.s, color.v);
- ok = true;
- format = "hsv";
- }
- else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) {
- color.s = convertToPercentage(color.s);
- color.l = convertToPercentage(color.l);
- rgb = hslToRgb(color.h, color.s, color.l);
- ok = true;
- format = "hsl";
- }
-
- if (color.hasOwnProperty("a")) {
- a = color.a;
- }
- }
-
- a = boundAlpha(a);
-
- return {
- ok: ok,
- format: color.format || format,
- r: mathMin(255, mathMax(rgb.r, 0)),
- g: mathMin(255, mathMax(rgb.g, 0)),
- b: mathMin(255, mathMax(rgb.b, 0)),
- a: a
- };
- }
-
-
- // Conversion Functions
- // --------------------
-
- // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
- //
-
- // `rgbToRgb`
- // Handle bounds / percentage checking to conform to CSS color spec
- //
- // *Assumes:* r, g, b in [0, 255] or [0, 1]
- // *Returns:* { r, g, b } in [0, 255]
- function rgbToRgb(r, g, b){
- return {
- r: bound01(r, 255) * 255,
- g: bound01(g, 255) * 255,
- b: bound01(b, 255) * 255
- };
- }
-
- // `rgbToHsl`
- // Converts an RGB color value to HSL.
- // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
- // *Returns:* { h, s, l } in [0,1]
- function rgbToHsl(r, g, b) {
-
- r = bound01(r, 255);
- g = bound01(g, 255);
- b = bound01(b, 255);
-
- var max = mathMax(r, g, b), min = mathMin(r, g, b);
- var h, s, l = (max + min) / 2;
-
- if(max == min) {
- h = s = 0; // achromatic
- }
- else {
- var d = max - min;
- s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
- switch(max) {
- case r: h = (g - b) / d + (g < b ? 6 : 0); break;
- case g: h = (b - r) / d + 2; break;
- case b: h = (r - g) / d + 4; break;
- }
-
- h /= 6;
- }
-
- return { h: h, s: s, l: l };
- }
-
- // `hslToRgb`
- // Converts an HSL color value to RGB.
- // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
- // *Returns:* { r, g, b } in the set [0, 255]
- function hslToRgb(h, s, l) {
- var r, g, b;
-
- h = bound01(h, 360);
- s = bound01(s, 100);
- l = bound01(l, 100);
-
- function hue2rgb(p, q, t) {
- if(t < 0) t += 1;
- if(t > 1) t -= 1;
- if(t < 1/6) return p + (q - p) * 6 * t;
- if(t < 1/2) return q;
- if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
- return p;
- }
-
- if(s === 0) {
- r = g = b = l; // achromatic
- }
- else {
- var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
- var p = 2 * l - q;
- r = hue2rgb(p, q, h + 1/3);
- g = hue2rgb(p, q, h);
- b = hue2rgb(p, q, h - 1/3);
- }
-
- return { r: r * 255, g: g * 255, b: b * 255 };
- }
-
- // `rgbToHsv`
- // Converts an RGB color value to HSV
- // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
- // *Returns:* { h, s, v } in [0,1]
- function rgbToHsv(r, g, b) {
-
- r = bound01(r, 255);
- g = bound01(g, 255);
- b = bound01(b, 255);
-
- var max = mathMax(r, g, b), min = mathMin(r, g, b);
- var h, s, v = max;
-
- var d = max - min;
- s = max === 0 ? 0 : d / max;
-
- if(max == min) {
- h = 0; // achromatic
- }
- else {
- switch(max) {
- case r: h = (g - b) / d + (g < b ? 6 : 0); break;
- case g: h = (b - r) / d + 2; break;
- case b: h = (r - g) / d + 4; break;
- }
- h /= 6;
- }
- return { h: h, s: s, v: v };
- }
-
- // `hsvToRgb`
- // Converts an HSV color value to RGB.
- // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
- // *Returns:* { r, g, b } in the set [0, 255]
- function hsvToRgb(h, s, v) {
-
- h = bound01(h, 360) * 6;
- s = bound01(s, 100);
- v = bound01(v, 100);
-
- var i = math.floor(h),
- f = h - i,
- p = v * (1 - s),
- q = v * (1 - f * s),
- t = v * (1 - (1 - f) * s),
- mod = i % 6,
- r = [v, q, p, p, t, v][mod],
- g = [t, v, v, q, p, p][mod],
- b = [p, p, t, v, v, q][mod];
-
- return { r: r * 255, g: g * 255, b: b * 255 };
- }
-
- // `rgbToHex`
- // Converts an RGB color to hex
- // Assumes r, g, and b are contained in the set [0, 255]
- // Returns a 3 or 6 character hex
- function rgbToHex(r, g, b, allow3Char) {
-
- var hex = [
- pad2(mathRound(r).toString(16)),
- pad2(mathRound(g).toString(16)),
- pad2(mathRound(b).toString(16))
- ];
-
- // Return a 3 character hex if possible
- if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
- return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
- }
-
- return hex.join("");
- }
- // `rgbaToHex`
- // Converts an RGBA color plus alpha transparency to hex
- // Assumes r, g, b and a are contained in the set [0, 255]
- // Returns an 8 character hex
- function rgbaToHex(r, g, b, a) {
-
- var hex = [
- pad2(convertDecimalToHex(a)),
- pad2(mathRound(r).toString(16)),
- pad2(mathRound(g).toString(16)),
- pad2(mathRound(b).toString(16))
- ];
-
- return hex.join("");
- }
-
- // `equals`
- // Can be called with any tinycolor input
- tinycolor.equals = function (color1, color2) {
- if (!color1 || !color2) { return false; }
- return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString();
- };
- tinycolor.random = function() {
- return tinycolor.fromRatio({
- r: mathRandom(),
- g: mathRandom(),
- b: mathRandom()
- });
- };
-
-
- // Modification Functions
- // ----------------------
- // Thanks to less.js for some of the basics here
- //
-
- function desaturate(color, amount) {
- amount = (amount === 0) ? 0 : (amount || 10);
- var hsl = tinycolor(color).toHsl();
- hsl.s -= amount / 100;
- hsl.s = clamp01(hsl.s);
- return tinycolor(hsl);
- }
-
- function saturate(color, amount) {
- amount = (amount === 0) ? 0 : (amount || 10);
- var hsl = tinycolor(color).toHsl();
- hsl.s += amount / 100;
- hsl.s = clamp01(hsl.s);
- return tinycolor(hsl);
- }
-
- function greyscale(color) {
- return tinycolor(color).desaturate(100);
- }
-
- function lighten (color, amount) {
- amount = (amount === 0) ? 0 : (amount || 10);
- var hsl = tinycolor(color).toHsl();
- hsl.l += amount / 100;
- hsl.l = clamp01(hsl.l);
- return tinycolor(hsl);
- }
-
- function brighten(color, amount) {
- amount = (amount === 0) ? 0 : (amount || 10);
- var rgb = tinycolor(color).toRgb();
- rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100))));
- rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100))));
- rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100))));
- return tinycolor(rgb);
- }
-
- function darken (color, amount) {
- amount = (amount === 0) ? 0 : (amount || 10);
- var hsl = tinycolor(color).toHsl();
- hsl.l -= amount / 100;
- hsl.l = clamp01(hsl.l);
- return tinycolor(hsl);
- }
-
- // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
- // Values outside of this range will be wrapped into this range.
- function spin(color, amount) {
- var hsl = tinycolor(color).toHsl();
- var hue = (mathRound(hsl.h) + amount) % 360;
- hsl.h = hue < 0 ? 360 + hue : hue;
- return tinycolor(hsl);
- }
-
- // Combination Functions
- // ---------------------
- // Thanks to jQuery xColor for some of the ideas behind these
- //
-
- function complement(color) {
- var hsl = tinycolor(color).toHsl();
- hsl.h = (hsl.h + 180) % 360;
- return tinycolor(hsl);
- }
-
- function triad(color) {
- var hsl = tinycolor(color).toHsl();
- var h = hsl.h;
- return [
- tinycolor(color),
- tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }),
- tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l })
- ];
- }
-
- function tetrad(color) {
- var hsl = tinycolor(color).toHsl();
- var h = hsl.h;
- return [
- tinycolor(color),
- tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }),
- tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }),
- tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l })
- ];
- }
-
- function splitcomplement(color) {
- var hsl = tinycolor(color).toHsl();
- var h = hsl.h;
- return [
- tinycolor(color),
- tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}),
- tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l})
- ];
- }
-
- function analogous(color, results, slices) {
- results = results || 6;
- slices = slices || 30;
-
- var hsl = tinycolor(color).toHsl();
- var part = 360 / slices;
- var ret = [tinycolor(color)];
-
- for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) {
- hsl.h = (hsl.h + part) % 360;
- ret.push(tinycolor(hsl));
- }
- return ret;
- }
-
- function monochromatic(color, results) {
- results = results || 6;
- var hsv = tinycolor(color).toHsv();
- var h = hsv.h, s = hsv.s, v = hsv.v;
- var ret = [];
- var modification = 1 / results;
-
- while (results--) {
- ret.push(tinycolor({ h: h, s: s, v: v}));
- v = (v + modification) % 1;
- }
-
- return ret;
- }
-
- // Utility Functions
- // ---------------------
-
- tinycolor.mix = function(color1, color2, amount) {
- amount = (amount === 0) ? 0 : (amount || 50);
-
- var rgb1 = tinycolor(color1).toRgb();
- var rgb2 = tinycolor(color2).toRgb();
-
- var p = amount / 100;
- var w = p * 2 - 1;
- var a = rgb2.a - rgb1.a;
-
- var w1;
-
- if (w * a == -1) {
- w1 = w;
- } else {
- w1 = (w + a) / (1 + w * a);
- }
-
- w1 = (w1 + 1) / 2;
-
- var w2 = 1 - w1;
-
- var rgba = {
- r: rgb2.r * w1 + rgb1.r * w2,
- g: rgb2.g * w1 + rgb1.g * w2,
- b: rgb2.b * w1 + rgb1.b * w2,
- a: rgb2.a * p + rgb1.a * (1 - p)
- };
-
- return tinycolor(rgba);
- };
-
-
- // Readability Functions
- // ---------------------
- //
-
- // `readability`
- // Analyze the 2 colors and returns an object with the following properties:
- // `brightness`: difference in brightness between the two colors
- // `color`: difference in color/hue between the two colors
- tinycolor.readability = function(color1, color2) {
- var c1 = tinycolor(color1);
- var c2 = tinycolor(color2);
- var rgb1 = c1.toRgb();
- var rgb2 = c2.toRgb();
- var brightnessA = c1.getBrightness();
- var brightnessB = c2.getBrightness();
- var colorDiff = (
- Math.max(rgb1.r, rgb2.r) - Math.min(rgb1.r, rgb2.r) +
- Math.max(rgb1.g, rgb2.g) - Math.min(rgb1.g, rgb2.g) +
- Math.max(rgb1.b, rgb2.b) - Math.min(rgb1.b, rgb2.b)
- );
-
- return {
- brightness: Math.abs(brightnessA - brightnessB),
- color: colorDiff
- };
- };
-
- // `readable`
- // http://www.w3.org/TR/AERT#color-contrast
- // Ensure that foreground and background color combinations provide sufficient contrast.
- // *Example*
- // tinycolor.isReadable("#000", "#111") => false
- tinycolor.isReadable = function(color1, color2) {
- var readability = tinycolor.readability(color1, color2);
- return readability.brightness > 125 && readability.color > 500;
- };
-
- // `mostReadable`
- // Given a base color and a list of possible foreground or background
- // colors for that base, returns the most readable color.
- // *Example*
- // tinycolor.mostReadable("#123", ["#fff", "#000"]) => "#000"
- tinycolor.mostReadable = function(baseColor, colorList) {
- var bestColor = null;
- var bestScore = 0;
- var bestIsReadable = false;
- for (var i=0; i < colorList.length; i++) {
-
- // We normalize both around the "acceptable" breaking point,
- // but rank brightness constrast higher than hue.
-
- var readability = tinycolor.readability(baseColor, colorList[i]);
- var readable = readability.brightness > 125 && readability.color > 500;
- var score = 3 * (readability.brightness / 125) + (readability.color / 500);
-
- if ((readable && ! bestIsReadable) ||
- (readable && bestIsReadable && score > bestScore) ||
- ((! readable) && (! bestIsReadable) && score > bestScore)) {
- bestIsReadable = readable;
- bestScore = score;
- bestColor = tinycolor(colorList[i]);
- }
- }
- return bestColor;
- };
-
-
- // Big List of Colors
- // ------------------
- //
- var names = tinycolor.names = {
- aliceblue: "f0f8ff",
- antiquewhite: "faebd7",
- aqua: "0ff",
- aquamarine: "7fffd4",
- azure: "f0ffff",
- beige: "f5f5dc",
- bisque: "ffe4c4",
- black: "000",
- blanchedalmond: "ffebcd",
- blue: "00f",
- blueviolet: "8a2be2",
- brown: "a52a2a",
- burlywood: "deb887",
- burntsienna: "ea7e5d",
- cadetblue: "5f9ea0",
- chartreuse: "7fff00",
- chocolate: "d2691e",
- coral: "ff7f50",
- cornflowerblue: "6495ed",
- cornsilk: "fff8dc",
- crimson: "dc143c",
- cyan: "0ff",
- darkblue: "00008b",
- darkcyan: "008b8b",
- darkgoldenrod: "b8860b",
- darkgray: "a9a9a9",
- darkgreen: "006400",
- darkgrey: "a9a9a9",
- darkkhaki: "bdb76b",
- darkmagenta: "8b008b",
- darkolivegreen: "556b2f",
- darkorange: "ff8c00",
- darkorchid: "9932cc",
- darkred: "8b0000",
- darksalmon: "e9967a",
- darkseagreen: "8fbc8f",
- darkslateblue: "483d8b",
- darkslategray: "2f4f4f",
- darkslategrey: "2f4f4f",
- darkturquoise: "00ced1",
- darkviolet: "9400d3",
- deeppink: "ff1493",
- deepskyblue: "00bfff",
- dimgray: "696969",
- dimgrey: "696969",
- dodgerblue: "1e90ff",
- firebrick: "b22222",
- floralwhite: "fffaf0",
- forestgreen: "228b22",
- fuchsia: "f0f",
- gainsboro: "dcdcdc",
- ghostwhite: "f8f8ff",
- gold: "ffd700",
- goldenrod: "daa520",
- gray: "808080",
- green: "008000",
- greenyellow: "adff2f",
- grey: "808080",
- honeydew: "f0fff0",
- hotpink: "ff69b4",
- indianred: "cd5c5c",
- indigo: "4b0082",
- ivory: "fffff0",
- khaki: "f0e68c",
- lavender: "e6e6fa",
- lavenderblush: "fff0f5",
- lawngreen: "7cfc00",
- lemonchiffon: "fffacd",
- lightblue: "add8e6",
- lightcoral: "f08080",
- lightcyan: "e0ffff",
- lightgoldenrodyellow: "fafad2",
- lightgray: "d3d3d3",
- lightgreen: "90ee90",
- lightgrey: "d3d3d3",
- lightpink: "ffb6c1",
- lightsalmon: "ffa07a",
- lightseagreen: "20b2aa",
- lightskyblue: "87cefa",
- lightslategray: "789",
- lightslategrey: "789",
- lightsteelblue: "b0c4de",
- lightyellow: "ffffe0",
- lime: "0f0",
- limegreen: "32cd32",
- linen: "faf0e6",
- magenta: "f0f",
- maroon: "800000",
- mediumaquamarine: "66cdaa",
- mediumblue: "0000cd",
- mediumorchid: "ba55d3",
- mediumpurple: "9370db",
- mediumseagreen: "3cb371",
- mediumslateblue: "7b68ee",
- mediumspringgreen: "00fa9a",
- mediumturquoise: "48d1cc",
- mediumvioletred: "c71585",
- midnightblue: "191970",
- mintcream: "f5fffa",
- mistyrose: "ffe4e1",
- moccasin: "ffe4b5",
- navajowhite: "ffdead",
- navy: "000080",
- oldlace: "fdf5e6",
- olive: "808000",
- olivedrab: "6b8e23",
- orange: "ffa500",
- orangered: "ff4500",
- orchid: "da70d6",
- palegoldenrod: "eee8aa",
- palegreen: "98fb98",
- paleturquoise: "afeeee",
- palevioletred: "db7093",
- papayawhip: "ffefd5",
- peachpuff: "ffdab9",
- peru: "cd853f",
- pink: "ffc0cb",
- plum: "dda0dd",
- powderblue: "b0e0e6",
- purple: "800080",
- rebeccapurple: "663399",
- red: "f00",
- rosybrown: "bc8f8f",
- royalblue: "4169e1",
- saddlebrown: "8b4513",
- salmon: "fa8072",
- sandybrown: "f4a460",
- seagreen: "2e8b57",
- seashell: "fff5ee",
- sienna: "a0522d",
- silver: "c0c0c0",
- skyblue: "87ceeb",
- slateblue: "6a5acd",
- slategray: "708090",
- slategrey: "708090",
- snow: "fffafa",
- springgreen: "00ff7f",
- steelblue: "4682b4",
- tan: "d2b48c",
- teal: "008080",
- thistle: "d8bfd8",
- tomato: "ff6347",
- turquoise: "40e0d0",
- violet: "ee82ee",
- wheat: "f5deb3",
- white: "fff",
- whitesmoke: "f5f5f5",
- yellow: "ff0",
- yellowgreen: "9acd32"
- };
-
- // Make it easy to access colors via `hexNames[hex]`
- var hexNames = tinycolor.hexNames = flip(names);
-
-
- // Utilities
- // ---------
-
- // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
- function flip(o) {
- var flipped = { };
- for (var i in o) {
- if (o.hasOwnProperty(i)) {
- flipped[o[i]] = i;
- }
- }
- return flipped;
- }
-
- // Return a valid alpha value [0,1] with all invalid values being set to 1
- function boundAlpha(a) {
- a = parseFloat(a);
-
- if (isNaN(a) || a < 0 || a > 1) {
- a = 1;
- }
-
- return a;
- }
-
- // Take input from [0, n] and return it as [0, 1]
- function bound01(n, max) {
- if (isOnePointZero(n)) { n = "100%"; }
-
- var processPercent = isPercentage(n);
- n = mathMin(max, mathMax(0, parseFloat(n)));
-
- // Automatically convert percentage into number
- if (processPercent) {
- n = parseInt(n * max, 10) / 100;
- }
-
- // Handle floating point rounding errors
- if ((math.abs(n - max) < 0.000001)) {
- return 1;
- }
-
- // Convert into [0, 1] range if it isn't already
- return (n % max) / parseFloat(max);
- }
-
- // Force a number between 0 and 1
- function clamp01(val) {
- return mathMin(1, mathMax(0, val));
- }
-
- // Parse a base-16 hex value into a base-10 integer
- function parseIntFromHex(val) {
- return parseInt(val, 16);
- }
-
- // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
- //
- function isOnePointZero(n) {
- return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
- }
-
- // Check to see if string passed in is a percentage
- function isPercentage(n) {
- return typeof n === "string" && n.indexOf('%') != -1;
- }
-
- // Force a hex value to have 2 characters
- function pad2(c) {
- return c.length == 1 ? '0' + c : '' + c;
- }
-
- // Replace a decimal with it's percentage value
- function convertToPercentage(n) {
- if (n <= 1) {
- n = (n * 100) + "%";
- }
-
- return n;
- }
-
- // Converts a decimal to a hex value
- function convertDecimalToHex(d) {
- return Math.round(parseFloat(d) * 255).toString(16);
- }
- // Converts a hex value to a decimal
- function convertHexToDecimal(h) {
- return (parseIntFromHex(h) / 255);
- }
-
- var matchers = (function() {
-
- //
- var CSS_INTEGER = "[-\\+]?\\d+%?";
-
- //
- var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";
-
- // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
- var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";
-
- // Actual matching.
- // Parentheses and commas are optional, but not required.
- // Whitespace can take the place of commas or opening paren
- var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
- var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
-
- return {
- rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
- rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
- hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
- hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
- hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
- hsva: new RegExp("hsva" + PERMISSIVE_MATCH4),
- hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
- hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
- hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
- };
- })();
-
- // `stringInputToObject`
- // Permissive string parsing. Take in a number of formats, and output an object
- // based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
- function stringInputToObject(color) {
-
- color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase();
- var named = false;
- if (names[color]) {
- color = names[color];
- named = true;
- }
- else if (color == 'transparent') {
- return { r: 0, g: 0, b: 0, a: 0, format: "name" };
- }
-
- // Try to match string input using regular expressions.
- // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
- // Just return an object and let the conversion functions handle that.
- // This way the result will be the same whether the tinycolor is initialized with string or object.
- var match;
- if ((match = matchers.rgb.exec(color))) {
- return { r: match[1], g: match[2], b: match[3] };
- }
- if ((match = matchers.rgba.exec(color))) {
- return { r: match[1], g: match[2], b: match[3], a: match[4] };
- }
- if ((match = matchers.hsl.exec(color))) {
- return { h: match[1], s: match[2], l: match[3] };
- }
- if ((match = matchers.hsla.exec(color))) {
- return { h: match[1], s: match[2], l: match[3], a: match[4] };
- }
- if ((match = matchers.hsv.exec(color))) {
- return { h: match[1], s: match[2], v: match[3] };
- }
- if ((match = matchers.hsva.exec(color))) {
- return { h: match[1], s: match[2], v: match[3], a: match[4] };
- }
- if ((match = matchers.hex8.exec(color))) {
- return {
- a: convertHexToDecimal(match[1]),
- r: parseIntFromHex(match[2]),
- g: parseIntFromHex(match[3]),
- b: parseIntFromHex(match[4]),
- format: named ? "name" : "hex8"
- };
- }
- if ((match = matchers.hex6.exec(color))) {
- return {
- r: parseIntFromHex(match[1]),
- g: parseIntFromHex(match[2]),
- b: parseIntFromHex(match[3]),
- format: named ? "name" : "hex"
- };
- }
- if ((match = matchers.hex3.exec(color))) {
- return {
- r: parseIntFromHex(match[1] + '' + match[1]),
- g: parseIntFromHex(match[2] + '' + match[2]),
- b: parseIntFromHex(match[3] + '' + match[3]),
- format: named ? "name" : "hex"
- };
- }
-
- return false;
- }
-
- window.tinycolor = tinycolor;
- })();
-
- $(function () {
- if ($.fn.spectrum.load) {
- $.fn.spectrum.processNativeColorInputs();
- }
- });
+ "use strict";
+
+ var defaultOpts = {
+
+ // Callbacks
+ beforeShow: noop,
+ move: noop,
+ change: noop,
+ show: noop,
+ hide: noop,
+
+ // Options
+ color: false,
+ flat: false,
+ showInput: false,
+ allowEmpty: false,
+ showButtons: true,
+ clickoutFiresChange: true,
+ showInitial: false,
+ showPalette: false,
+ showPaletteOnly: false,
+ hideAfterPaletteSelect: false,
+ togglePaletteOnly: false,
+ showSelectionPalette: true,
+ localStorageKey: false,
+ appendTo: "body",
+ maxSelectionSize: 7,
+ cancelText: 'clear',
+ chooseText: 'check',
+ togglePaletteMoreText: "more",
+ togglePaletteLessText: "less",
+ clearText: "Clear Color Selection",
+ noColorSelectedText: "No Color Selected",
+ preferredFormat: false,
+ className: "", // Deprecated - use containerClassName and replacerClassName instead.
+ containerClassName: "",
+ replacerClassName: "",
+ showAlpha: false,
+ theme: "sp-light",
+ palette: [["#ffffff", "#000000", "#ff0000", "#ff8000", "#ffff00", "#008000", "#0000ff", "#4b0082", "#9400d3"]],
+ selectionPalette: [],
+ disabled: false,
+ offset: null
+ },
+ spectrums = [],
+ IE = !!/msie/i.exec( window.navigator.userAgent ),
+ rgbaSupport = (function() {
+ function contains( str, substr ) {
+ return !!~('' + str).indexOf(substr);
+ }
+
+ var elem = document.createElement('div');
+ var style = elem.style;
+ style.cssText = 'background-color:rgba(0,0,0,.5)';
+ return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla');
+ })(),
+ replaceInput = [
+ ""
+ ].join(''),
+ markup = (function () {
+
+ // IE does not support gradients with multiple stops, so we need to simulate
+ // that for the rainbow slider with 8 divs that each have a single gradient
+ var gradientFix = "";
+ if (IE) {
+ for (var i = 1; i <= 6; i++) {
+ gradientFix += "";
+ }
+ }
+
+ return [
+ "",
+ "
",
+ "
",
+ "
",
+ "",
+ "
",
+ "
",
+ "
",
+ "
",
+ "
",
+ "
",
+ "
",
+ "
",
+ "
",
+ "
",
+ "
",
+ gradientFix,
+ "
",
+ "
",
+ "
",
+ "
",
+ "
",
+ "",
+ "
",
+ "
",
+ "
",
+ "
",
+ "
"
+ ].join("");
+ })();
+
+ function paletteTemplate (p, color, className, opts) {
+ var html = [];
+ for (var i = 0; i < p.length; i++) {
+ var current = p[i];
+ if(current) {
+ var tiny = tinycolor(current);
+ var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light";
+ c += (tinycolor.equals(color, current)) ? " sp-thumb-active" : "";
+ var formattedString = tiny.toString(opts.preferredFormat || "rgb");
+ var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter();
+ html.push('');
+ } else {
+ var cls = 'sp-clear-display';
+ html.push($('')
+ .append($('')
+ .attr('title', opts.noColorSelectedText)
+ )
+ .html()
+ );
+ }
+ }
+ return "" + html.join('') + "
";
+ }
+
+ function hideAll() {
+ for (var i = 0; i < spectrums.length; i++) {
+ if (spectrums[i]) {
+ spectrums[i].hide();
+ }
+ }
+ }
+
+ function instanceOptions(o, callbackContext) {
+ var opts = $.extend({}, defaultOpts, o);
+ opts.callbacks = {
+ 'move': bind(opts.move, callbackContext),
+ 'change': bind(opts.change, callbackContext),
+ 'show': bind(opts.show, callbackContext),
+ 'hide': bind(opts.hide, callbackContext),
+ 'beforeShow': bind(opts.beforeShow, callbackContext)
+ };
+
+ return opts;
+ }
+
+ function spectrum(element, o) {
+
+ var opts = instanceOptions(o, element),
+ flat = opts.flat,
+ showSelectionPalette = opts.showSelectionPalette,
+ localStorageKey = opts.localStorageKey,
+ theme = opts.theme,
+ callbacks = opts.callbacks,
+ resize = throttle(reflow, 10),
+ visible = false,
+ isDragging = false,
+ dragWidth = 0,
+ dragHeight = 0,
+ dragHelperHeight = 0,
+ slideHeight = 0,
+ slideWidth = 0,
+ alphaWidth = 0,
+ alphaSlideHelperWidth = 0,
+ slideHelperHeight = 0,
+ currentHue = 0,
+ currentSaturation = 0,
+ currentValue = 0,
+ currentAlpha = 1,
+ palette = [],
+ paletteArray = [],
+ paletteLookup = {},
+ selectionPalette = opts.selectionPalette.slice(0),
+ maxSelectionSize = opts.maxSelectionSize,
+ draggingClass = "sp-dragging",
+ shiftMovementDirection = null;
+
+ var doc = element.ownerDocument,
+ body = doc.body,
+ boundElement = $(element),
+ disabled = false,
+ container = $(markup, doc).addClass(theme),
+ pickerContainer = container.find(".sp-picker-container"),
+ dragger = container.find(".sp-color"),
+ dragHelper = container.find(".sp-dragger"),
+ slider = container.find(".sp-hue"),
+ slideHelper = container.find(".sp-slider"),
+ alphaSliderInner = container.find(".sp-alpha-inner"),
+ alphaSlider = container.find(".sp-alpha"),
+ alphaSlideHelper = container.find(".sp-alpha-handle"),
+ textInput = container.find(".sp-input"),
+ paletteContainer = container.find(".sp-palette"),
+ initialColorContainer = container.find(".sp-initial"),
+ cancelButton = container.find(".sp-cancel"),
+ clearButton = container.find(".sp-clear"),
+ chooseButton = container.find(".sp-choose"),
+ toggleButton = container.find(".sp-palette-toggle"),
+ isInput = boundElement.is("input"),
+ isInputTypeColor = isInput && boundElement.attr("type") === "color" && inputTypeColorSupport(),
+ shouldReplace = isInput && !flat,
+ replacer = (shouldReplace) ? $(replaceInput).addClass(theme).addClass(opts.className).addClass(opts.replacerClassName) : $([]),
+ offsetElement = (shouldReplace) ? replacer : boundElement,
+ previewElement = replacer.find(".sp-preview-inner"),
+ initialColor = opts.color || (isInput && boundElement.val()),
+ colorOnShow = false,
+ currentPreferredFormat = opts.preferredFormat,
+ clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange,
+ isEmpty = !initialColor,
+ allowEmpty = opts.allowEmpty && !isInputTypeColor;
+
+ function applyOptions() {
+
+ if (opts.showPaletteOnly) {
+ opts.showPalette = true;
+ }
+
+ toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText);
+
+ if (opts.palette) {
+ palette = opts.palette.slice(0);
+ paletteArray = $.isArray(palette[0]) ? palette : [palette];
+ paletteLookup = {};
+ for (var i = 0; i < paletteArray.length; i++) {
+ for (var j = 0; j < paletteArray[i].length; j++) {
+ var rgb = tinycolor(paletteArray[i][j]).toRgbString();
+ paletteLookup[rgb] = true;
+ }
+ }
+ }
+
+ container.toggleClass("sp-flat", flat);
+ container.toggleClass("sp-input-disabled", !opts.showInput);
+ container.toggleClass("sp-alpha-enabled", opts.showAlpha);
+ container.toggleClass("sp-clear-enabled", allowEmpty);
+ container.toggleClass("sp-buttons-disabled", !opts.showButtons);
+ container.toggleClass("sp-palette-buttons-disabled", !opts.togglePaletteOnly);
+ container.toggleClass("sp-palette-disabled", !opts.showPalette);
+ container.toggleClass("sp-palette-only", opts.showPaletteOnly);
+ container.toggleClass("sp-initial-disabled", !opts.showInitial);
+ container.addClass(opts.className).addClass(opts.containerClassName);
+
+ reflow();
+ }
+
+ function initialize() {
+
+ if (IE) {
+ container.find("*:not(input)").attr("unselectable", "on");
+ }
+
+ applyOptions();
+
+ if (shouldReplace) {
+ boundElement.after(replacer).hide();
+ }
+
+ if (!allowEmpty) {
+ clearButton.hide();
+ }
+
+ if (flat) {
+ boundElement.after(container).hide();
+ }
+ else {
+
+ var appendTo = opts.appendTo === "parent" ? boundElement.parent() : $(opts.appendTo);
+ if (appendTo.length !== 1) {
+ appendTo = $("body");
+ }
+
+ appendTo.append(container);
+ }
+
+ updateSelectionPaletteFromStorage();
+
+ offsetElement.bind("click.spectrum touchstart.spectrum", function (e) {
+ if (!disabled) {
+ toggle();
+ }
+
+ e.stopPropagation();
+
+ if (!$(e.target).is("input")) {
+ e.preventDefault();
+ }
+ });
+
+ if(boundElement.is(":disabled") || (opts.disabled === true)) {
+ disable();
+ }
+
+ // Prevent clicks from bubbling up to document. This would cause it to be hidden.
+ container.click(stopPropagation);
+
+ // Handle user typed input
+ textInput.change(setFromTextInput);
+ textInput.bind("paste", function () {
+ setTimeout(setFromTextInput, 1);
+ });
+ textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } });
+
+ cancelButton.html(opts.cancelText);
+ cancelButton.bind("click.spectrum", function (e) {
+ e.stopPropagation();
+ e.preventDefault();
+ revert();
+ hide();
+ });
+
+ clearButton.attr("title", opts.clearText);
+ clearButton.bind("click.spectrum", function (e) {
+ e.stopPropagation();
+ e.preventDefault();
+ isEmpty = true;
+ move();
+
+ if(flat) {
+ //for the flat style, this is a change event
+ updateOriginalInput(true);
+ }
+ });
+
+ chooseButton.html(opts.chooseText);
+ chooseButton.bind("click.spectrum", function (e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ if (IE && textInput.is(":focus")) {
+ textInput.trigger('change');
+ }
+
+ if (isValid()) {
+ updateOriginalInput(true);
+ hide();
+ }
+ });
+
+ toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText);
+ toggleButton.bind("click.spectrum", function (e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ opts.showPaletteOnly = !opts.showPaletteOnly;
+
+ // To make sure the Picker area is drawn on the right, next to the
+ // Palette area (and not below the palette), first move the Palette
+ // to the left to make space for the picker, plus 5px extra.
+ // The 'applyOptions' function puts the whole container back into place
+ // and takes care of the button-text and the sp-palette-only CSS class.
+ if (!opts.showPaletteOnly && !flat) {
+ container.css('left', '-=' + (pickerContainer.outerWidth(true) + 5));
+ }
+ applyOptions();
+ });
+
+ draggable(alphaSlider, function (dragX, dragY, e) {
+ currentAlpha = (dragX / alphaWidth);
+ isEmpty = false;
+ if (e.shiftKey) {
+ currentAlpha = Math.round(currentAlpha * 10) / 10;
+ }
+
+ move();
+ }, dragStart, dragStop);
+
+ draggable(slider, function (dragX, dragY) {
+ currentHue = parseFloat(dragY / slideHeight);
+ isEmpty = false;
+ if (!opts.showAlpha) {
+ currentAlpha = 1;
+ }
+ move();
+ }, dragStart, dragStop);
+
+ draggable(dragger, function (dragX, dragY, e) {
+
+ // shift+drag should snap the movement to either the x or y axis.
+ if (!e.shiftKey) {
+ shiftMovementDirection = null;
+ }
+ else if (!shiftMovementDirection) {
+ var oldDragX = currentSaturation * dragWidth;
+ var oldDragY = dragHeight - (currentValue * dragHeight);
+ var furtherFromX = Math.abs(dragX - oldDragX) > Math.abs(dragY - oldDragY);
+
+ shiftMovementDirection = furtherFromX ? "x" : "y";
+ }
+
+ var setSaturation = !shiftMovementDirection || shiftMovementDirection === "x";
+ var setValue = !shiftMovementDirection || shiftMovementDirection === "y";
+
+ if (setSaturation) {
+ currentSaturation = parseFloat(dragX / dragWidth);
+ }
+ if (setValue) {
+ currentValue = parseFloat((dragHeight - dragY) / dragHeight);
+ }
+
+ isEmpty = false;
+ if (!opts.showAlpha) {
+ currentAlpha = 1;
+ }
+
+ move();
+
+ }, dragStart, dragStop);
+
+ if (!!initialColor) {
+ set(initialColor);
+
+ // In case color was black - update the preview UI and set the format
+ // since the set function will not run (default color is black).
+ updateUI();
+ currentPreferredFormat = opts.preferredFormat || tinycolor(initialColor).format;
+
+ addColorToSelectionPalette(initialColor);
+ }
+ else {
+ updateUI();
+ }
+
+ if (flat) {
+ show();
+ }
+
+ function paletteElementClick(e) {
+ if (e.data && e.data.ignore) {
+ set($(e.target).closest(".sp-thumb-el").data("color"));
+ move();
+ } else if (event.ctrlKey) {
+
+ var index = parseInt($(e.target).closest(".sp-thumb-el").data("index"));
+ cl(index)
+ if (!isNaN(index) && index >= 0) {
+ selectionPalette.splice(selectionPalette.length-index-1, 1);
+ if (localStorageKey && window.localStorage) {
+ try {
+ window.localStorage[localStorageKey] = selectionPalette.join(";");
+ }
+ catch(e) { }
+ }
+ updateUI()
+ }
+
+ } else {
+ set($(e.target).closest(".sp-thumb-el").data("color"));
+ move();
+ updateOriginalInput(true);
+ if (opts.hideAfterPaletteSelect) {
+ hide();
+ }
+ }
+
+ return false;
+ }
+
+ var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum";
+ paletteContainer.delegate(".sp-thumb-el", paletteEvent, paletteElementClick);
+ initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, paletteElementClick);
+ }
+
+ function updateSelectionPaletteFromStorage() {
+
+ if (localStorageKey && window.localStorage) {
+
+ // Migrate old palettes over to new format. May want to remove this eventually.
+ try {
+ var oldPalette = window.localStorage[localStorageKey].split(",#");
+ if (oldPalette.length > 1) {
+ delete window.localStorage[localStorageKey];
+ $.each(oldPalette, function(i, c) {
+ addColorToSelectionPalette(c);
+ });
+ }
+ }
+ catch(e) { }
+
+ try {
+ selectionPalette = window.localStorage[localStorageKey].split(";");
+ }
+ catch (e) { }
+ }
+ }
+
+ function addColorToSelectionPalette(color) {
+
+ if (showSelectionPalette) {
+ var rgb = tinycolor(color).toRgbString();
+ if (!paletteLookup[rgb] && $.inArray(rgb, selectionPalette) === -1) {
+ selectionPalette.push(rgb);
+ while(selectionPalette.length > maxSelectionSize) {
+ selectionPalette.shift();
+ }
+ }
+
+ if (localStorageKey && window.localStorage) {
+ try {
+ window.localStorage[localStorageKey] = selectionPalette.join(";");
+ }
+ catch(e) { }
+ }
+ }
+ }
+
+ function getUniqueSelectionPalette() {
+ var unique = [];
+ if (opts.showPalette) {
+ for (var i = 0; i < selectionPalette.length; i++) {
+ var rgb = tinycolor(selectionPalette[i]).toRgbString();
+
+ if (!paletteLookup[rgb]) {
+ unique.push(selectionPalette[i]);
+ }
+ }
+ }
+
+ return unique.reverse().slice(0, opts.maxSelectionSize);
+ }
+
+ function drawPalette() {
+
+ var currentColor = get();
+
+ var html = $.map(paletteArray, function (palette, i) {
+ return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i, opts);
+ });
+
+ updateSelectionPaletteFromStorage();
+
+ if (selectionPalette) {
+ html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection", opts));
+ }
+
+ paletteContainer.html(html.join(""));
+ }
+
+ function drawInitial() {
+ if (opts.showInitial) {
+ var initial = colorOnShow;
+ var current = get();
+ initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial", opts));
+ }
+ }
+
+ function dragStart() {
+ if (dragHeight <= 0 || dragWidth <= 0 || slideHeight <= 0 || flat) {
+ reflow();
+ }
+ isDragging = true;
+ container.addClass(draggingClass);
+ shiftMovementDirection = null;
+ boundElement.trigger('dragstart.spectrum', [ get() ]);
+ }
+
+ function dragStop() {
+ isDragging = false;
+ container.removeClass(draggingClass);
+ boundElement.trigger('dragstop.spectrum', [ get() ]);
+ }
+
+ function setFromTextInput() {
+
+ var value = textInput.val();
+
+ if ((value === null || value === "") && allowEmpty) {
+ set(null);
+ updateOriginalInput(true);
+ }
+ else {
+ var tiny = tinycolor(value);
+ if (tiny.isValid()) {
+ set(tiny);
+ updateOriginalInput(true);
+ }
+ else {
+ textInput.addClass("sp-validation-error");
+ }
+ }
+ }
+
+ function toggle() {
+ if (visible) {
+ hide();
+ }
+ else {
+ show();
+ }
+ }
+
+ function show() {
+ var event = $.Event('beforeShow.spectrum');
+
+ if (visible) {
+ reflow();
+ return;
+ }
+
+ boundElement.trigger(event, [ get() ]);
+
+ if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) {
+ return;
+ }
+
+ hideAll();
+ visible = true;
+
+ $(doc).bind("keydown.spectrum", onkeydown);
+ $(doc).bind("click.spectrum", clickout);
+ $(window).bind("resize.spectrum", resize);
+ replacer.addClass("sp-active");
+ container.removeClass("sp-hidden");
+
+ reflow();
+ updateUI();
+
+ colorOnShow = get();
+
+ drawInitial();
+ callbacks.show(colorOnShow);
+ boundElement.trigger('show.spectrum', [ colorOnShow ]);
+ }
+
+ function onkeydown(e) {
+ // Close on ESC
+ if (e.keyCode === 27) {
+ hide();
+ }
+ }
+
+ function clickout(e) {
+ // Return on right click.
+ if (e.button == 2) { return; }
+
+ // If a drag event was happening during the mouseup, don't hide
+ // on click.
+ if (isDragging) { return; }
+
+ if (clickoutFiresChange) {
+ updateOriginalInput(true);
+ }
+ else {
+ revert();
+ }
+ hide();
+ }
+
+ function hide() {
+ // Return if hiding is unnecessary
+ if (!visible || flat) { return; }
+ visible = false;
+
+
+ if (isValid()) {
+ updateOriginalInput(true);
+ }
+
+ $(doc).unbind("keydown.spectrum", onkeydown);
+ $(doc).unbind("click.spectrum", clickout);
+ $(window).unbind("resize.spectrum", resize);
+
+ replacer.removeClass("sp-active");
+ container.addClass("sp-hidden");
+
+ callbacks.hide(get());
+ boundElement.trigger('hide.spectrum', [ get() ]);
+ }
+
+ function cancel() {
+ // Return if hiding is unnecessary
+ if (!visible || flat) { return; }
+ revert()
+ visible = false;
+
+ $(doc).unbind("keydown.spectrum", onkeydown);
+ $(doc).unbind("click.spectrum", clickout);
+ $(window).unbind("resize.spectrum", resize);
+
+ replacer.removeClass("sp-active");
+ container.addClass("sp-hidden");
+
+ callbacks.hide(get());
+ boundElement.trigger('hide.spectrum', [ get() ]);
+ }
+
+ function revert() {
+ set(colorOnShow, true);
+ }
+
+ function set(color, ignoreFormatChange) {
+ if (tinycolor.equals(color, get())) {
+ // Update UI just in case a validation error needs
+ // to be cleared.
+ updateUI();
+ return;
+ }
+
+ var newColor, newHsv;
+ if (!color && allowEmpty) {
+ isEmpty = true;
+ } else {
+ isEmpty = false;
+ newColor = tinycolor(color);
+ newHsv = newColor.toHsv();
+
+ currentHue = (newHsv.h % 360) / 360;
+ currentSaturation = newHsv.s;
+ currentValue = newHsv.v;
+ currentAlpha = newHsv.a;
+ }
+ updateUI();
+
+ if (newColor && newColor.isValid() && !ignoreFormatChange) {
+ currentPreferredFormat = opts.preferredFormat || newColor.getFormat();
+ }
+ }
+
+ function get(opts) {
+ opts = opts || { };
+
+ if (allowEmpty && isEmpty) {
+ return null;
+ }
+
+ return tinycolor.fromRatio({
+ h: currentHue,
+ s: currentSaturation,
+ v: currentValue,
+ a: Math.round(currentAlpha * 100) / 100
+ }, { format: opts.format || currentPreferredFormat });
+ }
+
+ function isValid() {
+ return !textInput.hasClass("sp-validation-error");
+ }
+
+ function move() {
+ updateUI();
+
+ callbacks.move(get());
+ boundElement.trigger('move.spectrum', [ get() ]);
+ }
+
+ function updateUI() {
+
+ textInput.removeClass("sp-validation-error");
+
+ updateHelperLocations();
+
+ // Update dragger background color (gradients take care of saturation and value).
+ var flatColor = tinycolor.fromRatio({ h: currentHue, s: 1, v: 1 });
+ dragger.css("background-color", flatColor.toHexString());
+
+ // Get a format that alpha will be included in (hex and names ignore alpha)
+ var format = currentPreferredFormat;
+ if (currentAlpha < 1 && !(currentAlpha === 0 && format === "name")) {
+ if (format === "hex" || format === "hex3" || format === "hex6" || format === "name") {
+ format = "rgb";
+ }
+ }
+
+ var realColor = get({ format: format }),
+ displayColor = '';
+
+ //reset background info for preview element
+ previewElement.removeClass("sp-clear-display");
+ previewElement.css('background-color', 'transparent');
+
+ if (!realColor && allowEmpty) {
+ // Update the replaced elements background with icon indicating no color selection
+ previewElement.addClass("sp-clear-display");
+ }
+ else {
+ var realHex = realColor.toHexString(),
+ realRgb = realColor.toRgbString();
+
+ // Update the replaced elements background color (with actual selected color)
+ if (rgbaSupport || realColor.alpha === 1) {
+ previewElement.css("background-color", realRgb);
+ }
+ else {
+ previewElement.css("background-color", "transparent");
+ previewElement.css("filter", realColor.toFilter());
+ }
+
+ if (opts.showAlpha) {
+ var rgb = realColor.toRgb();
+ rgb.a = 0;
+ var realAlpha = tinycolor(rgb).toRgbString();
+ var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")";
+
+ if (IE) {
+ alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({ gradientType: 1 }, realHex));
+ }
+ else {
+ alphaSliderInner.css("background", "-webkit-" + gradient);
+ alphaSliderInner.css("background", "-moz-" + gradient);
+ alphaSliderInner.css("background", "-ms-" + gradient);
+ // Use current syntax gradient on unprefixed property.
+ alphaSliderInner.css("background",
+ "linear-gradient(to right, " + realAlpha + ", " + realHex + ")");
+ }
+ }
+
+ displayColor = realColor.toString(format);
+ }
+
+ // Update the text entry input as it changes happen
+ if (opts.showInput) {
+ textInput.val(displayColor);
+ }
+
+ if (opts.showPalette) {
+ drawPalette();
+ }
+
+ drawInitial();
+ }
+
+ function updateHelperLocations() {
+ var s = currentSaturation;
+ var v = currentValue;
+
+ if(allowEmpty && isEmpty) {
+ //if selected color is empty, hide the helpers
+ alphaSlideHelper.hide();
+ slideHelper.hide();
+ dragHelper.hide();
+ }
+ else {
+ //make sure helpers are visible
+ alphaSlideHelper.show();
+ slideHelper.show();
+ dragHelper.show();
+
+ // Where to show the little circle in that displays your current selected color
+ var dragX = s * dragWidth;
+ var dragY = dragHeight - (v * dragHeight);
+ dragX = Math.max(
+ -dragHelperHeight,
+ Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight)
+ );
+ dragY = Math.max(
+ -dragHelperHeight,
+ Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight)
+ );
+ dragHelper.css({
+ "top": dragY + "px",
+ "left": dragX + "px"
+ });
+
+ var alphaX = currentAlpha * alphaWidth;
+ alphaSlideHelper.css({
+ "left": (alphaX - (alphaSlideHelperWidth / 2)) + "px"
+ });
+
+ // Where to show the bar that displays your current selected hue
+ var slideY = (currentHue) * slideHeight;
+ slideHelper.css({
+ "top": (slideY - slideHelperHeight) + "px"
+ });
+ }
+ }
+
+ function updateOriginalInput(fireCallback) {
+ var color = get(),
+ displayColor = '',
+ hasChanged = !tinycolor.equals(color, colorOnShow);
+
+ if (color) {
+ displayColor = color.toString(currentPreferredFormat);
+ // Update the selection palette with the current color
+ addColorToSelectionPalette(color);
+ }
+
+ if (isInput) {
+ boundElement.val(displayColor);
+ }
+
+ if (fireCallback && hasChanged) {
+ callbacks.change(color);
+ boundElement.trigger('change', [ color ]);
+ }
+ }
+
+ function reflow() {
+ if (!visible) {
+ return; // Calculations would be useless and wouldn't be reliable anyways
+ }
+ dragWidth = dragger.width();
+ dragHeight = dragger.height();
+ dragHelperHeight = dragHelper.height();
+ slideWidth = slider.width();
+ slideHeight = slider.height();
+ slideHelperHeight = slideHelper.height();
+ alphaWidth = alphaSlider.width();
+ alphaSlideHelperWidth = alphaSlideHelper.width();
+
+ if (!flat) {
+ container.css("position", "absolute");
+ if (opts.offset) {
+ container.offset(opts.offset);
+ } else {
+ container.offset(getOffset(container, offsetElement));
+ }
+ }
+
+ updateHelperLocations();
+
+ if (opts.showPalette) {
+ drawPalette();
+ }
+
+ boundElement.trigger('reflow.spectrum');
+ }
+
+ function destroy() {
+ boundElement.show();
+ offsetElement.unbind("click.spectrum touchstart.spectrum");
+ container.remove();
+ replacer.remove();
+ spectrums[spect.id] = null;
+ }
+
+ function option(optionName, optionValue) {
+ if (optionName === undefined) {
+ return $.extend({}, opts);
+ }
+ if (optionValue === undefined) {
+ return opts[optionName];
+ }
+
+ opts[optionName] = optionValue;
+
+ if (optionName === "preferredFormat") {
+ currentPreferredFormat = opts.preferredFormat;
+ }
+ applyOptions();
+ }
+
+ function enable() {
+ disabled = false;
+ boundElement.attr("disabled", false);
+ offsetElement.removeClass("sp-disabled");
+ }
+
+ function disable() {
+ hide();
+ disabled = true;
+ boundElement.attr("disabled", true);
+ offsetElement.addClass("sp-disabled");
+ }
+
+ function setOffset(coord) {
+ opts.offset = coord;
+ reflow();
+ }
+
+ initialize();
+
+ var spect = {
+ show: show,
+ hide: hide,
+ cancel: cancel,
+ toggle: toggle,
+ reflow: reflow,
+ option: option,
+ enable: enable,
+ disable: disable,
+ offset: setOffset,
+ set: function (c) {
+ set(c);
+ updateOriginalInput();
+ },
+ get: get,
+ destroy: destroy,
+ container: container
+ };
+
+ spect.id = spectrums.push(spect) - 1;
+
+ return spect;
+ }
+
+ /**
+ * checkOffset - get the offset below/above and left/right element depending on screen position
+ * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js
+ */
+ function getOffset(picker, input) {
+ var extraY = 0;
+ var dpWidth = picker.outerWidth();
+ var dpHeight = picker.outerHeight();
+ var inputHeight = input.outerHeight();
+ var doc = picker[0].ownerDocument;
+ var docElem = doc.documentElement;
+ var viewWidth = docElem.clientWidth + $(doc).scrollLeft();
+ var viewHeight = docElem.clientHeight + $(doc).scrollTop();
+ var offset = input.offset();
+ offset.top += inputHeight;
+
+ offset.left -=
+ Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
+ Math.abs(offset.left + dpWidth - viewWidth) : 0);
+
+ offset.top -=
+ Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
+ Math.abs(dpHeight + inputHeight - extraY) : extraY));
+
+ return offset;
+ }
+
+ /**
+ * noop - do nothing
+ */
+ function noop() {
+
+ }
+
+ /**
+ * stopPropagation - makes the code only doing this a little easier to read in line
+ */
+ function stopPropagation(e) {
+ e.stopPropagation();
+ }
+
+ /**
+ * Create a function bound to a given object
+ * Thanks to underscore.js
+ */
+ function bind(func, obj) {
+ var slice = Array.prototype.slice;
+ var args = slice.call(arguments, 2);
+ return function () {
+ return func.apply(obj, args.concat(slice.call(arguments)));
+ };
+ }
+
+ /**
+ * Lightweight drag helper. Handles containment within the element, so that
+ * when dragging, the x is within [0,element.width] and y is within [0,element.height]
+ */
+ function draggable(element, onmove, onstart, onstop) {
+ onmove = onmove || function () { };
+ onstart = onstart || function () { };
+ onstop = onstop || function () { };
+ var doc = document;
+ var dragging = false;
+ var offset = {};
+ var maxHeight = 0;
+ var maxWidth = 0;
+ var hasTouch = ('ontouchstart' in window);
+
+ var duringDragEvents = {};
+ duringDragEvents["selectstart"] = prevent;
+ duringDragEvents["dragstart"] = prevent;
+ duringDragEvents["touchmove mousemove"] = move;
+ duringDragEvents["touchend mouseup"] = stop;
+
+ function prevent(e) {
+ if (e.stopPropagation) {
+ e.stopPropagation();
+ }
+ if (e.preventDefault) {
+ e.preventDefault();
+ }
+ e.returnValue = false;
+ }
+
+ function move(e) {
+ if (dragging) {
+ // Mouseup happened outside of window
+ if (IE && doc.documentMode < 9 && !e.button) {
+ return stop();
+ }
+
+ var t0 = e.originalEvent && e.originalEvent.touches && e.originalEvent.touches[0];
+ var pageX = t0 && t0.pageX || e.pageX;
+ var pageY = t0 && t0.pageY || e.pageY;
+
+ var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth));
+ var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight));
+
+ if (hasTouch) {
+ // Stop scrolling in iOS
+ prevent(e);
+ }
+
+ onmove.apply(element, [dragX, dragY, e]);
+ }
+ }
+
+ function start(e) {
+ var rightclick = (e.which) ? (e.which == 3) : (e.button == 2);
+
+ if (!rightclick && !dragging) {
+ if (onstart.apply(element, arguments) !== false) {
+ dragging = true;
+ maxHeight = $(element).height();
+ maxWidth = $(element).width();
+ offset = $(element).offset();
+
+ $(doc).bind(duringDragEvents);
+ $(doc.body).addClass("sp-dragging");
+
+ move(e);
+
+ prevent(e);
+ }
+ }
+ }
+
+ function stop() {
+ if (dragging) {
+ $(doc).unbind(duringDragEvents);
+ $(doc.body).removeClass("sp-dragging");
+
+ // Wait a tick before notifying observers to allow the click event
+ // to fire in Chrome.
+ setTimeout(function() {
+ onstop.apply(element, arguments);
+ }, 0);
+ }
+ dragging = false;
+ }
+
+ $(element).bind("touchstart mousedown", start);
+ }
+
+ function throttle(func, wait, debounce) {
+ var timeout;
+ return function () {
+ var context = this, args = arguments;
+ var throttler = function () {
+ timeout = null;
+ func.apply(context, args);
+ };
+ if (debounce) clearTimeout(timeout);
+ if (debounce || !timeout) timeout = setTimeout(throttler, wait);
+ };
+ }
+
+ function inputTypeColorSupport() {
+ return $.fn.spectrum.inputTypeColorSupport();
+ }
+
+ /**
+ * Define a jQuery plugin
+ */
+ var dataID = "spectrum.id";
+ $.fn.spectrum = function (opts, extra) {
+
+ if (typeof opts == "string") {
+
+ var returnValue = this;
+ var args = Array.prototype.slice.call( arguments, 1 );
+
+ this.each(function () {
+ var spect = spectrums[$(this).data(dataID)];
+ if (spect) {
+ var method = spect[opts];
+ if (!method) {
+ throw new Error( "Spectrum: no such method: '" + opts + "'" );
+ }
+
+ if (opts == "get") {
+ returnValue = spect.get();
+ }
+ else if (opts == "container") {
+ returnValue = spect.container;
+ }
+ else if (opts == "option") {
+ returnValue = spect.option.apply(spect, args);
+ }
+ else if (opts == "destroy") {
+ spect.destroy();
+ $(this).removeData(dataID);
+ }
+ else if (opts == "cancel") {
+ spect.cancel();
+ }
+ else {
+ method.apply(spect, args);
+ }
+ }
+ });
+
+ return returnValue;
+ }
+
+ // Initializing a new instance of spectrum
+ return this.spectrum("destroy").each(function () {
+ var options = $.extend({}, opts, $(this).data());
+ var spect = spectrum(this, options);
+ $(this).data(dataID, spect.id);
+ });
+ };
+
+ $.fn.spectrum.load = true;
+ $.fn.spectrum.loadOpts = {};
+ $.fn.spectrum.draggable = draggable;
+ $.fn.spectrum.defaults = defaultOpts;
+ $.fn.spectrum.inputTypeColorSupport = function inputTypeColorSupport() {
+ if (typeof inputTypeColorSupport._cachedResult === "undefined") {
+ var colorInput = $("")[0]; // if color element is supported, value will default to not null
+ inputTypeColorSupport._cachedResult = colorInput.type === "color" && colorInput.value !== "";
+ }
+ return inputTypeColorSupport._cachedResult;
+ };
+
+ $.spectrum = { };
+ $.spectrum.localization = { };
+ $.spectrum.palettes = { };
+
+ $.fn.spectrum.processNativeColorInputs = function () {
+ var colorInputs = $("input[type=color]");
+ if (colorInputs.length && !inputTypeColorSupport()) {
+ colorInputs.spectrum({
+ preferredFormat: "hex6"
+ });
+ }
+ };
+
+ // TinyColor v1.1.2
+ // https://github.com/bgrins/TinyColor
+ // Brian Grinstead, MIT License
+
+ (function() {
+
+ var trimLeft = /^[\s,#]+/,
+ trimRight = /\s+$/,
+ tinyCounter = 0,
+ math = Math,
+ mathRound = math.round,
+ mathMin = math.min,
+ mathMax = math.max,
+ mathRandom = math.random;
+
+ var tinycolor = function(color, opts) {
+
+ color = (color) ? color : '';
+ opts = opts || { };
+
+ // If input is already a tinycolor, return itself
+ if (color instanceof tinycolor) {
+ return color;
+ }
+ // If we are called as a function, call using new instead
+ if (!(this instanceof tinycolor)) {
+ return new tinycolor(color, opts);
+ }
+
+ var rgb = inputToRGB(color);
+ this._originalInput = color,
+ this._r = rgb.r,
+ this._g = rgb.g,
+ this._b = rgb.b,
+ this._a = rgb.a,
+ this._roundA = mathRound(100*this._a) / 100,
+ this._format = opts.format || rgb.format;
+ this._gradientType = opts.gradientType;
+
+ // Don't let the range of [0,255] come back in [0,1].
+ // Potentially lose a little bit of precision here, but will fix issues where
+ // .5 gets interpreted as half of the total, instead of half of 1
+ // If it was supposed to be 128, this was already taken care of by `inputToRgb`
+ if (this._r < 1) { this._r = mathRound(this._r); }
+ if (this._g < 1) { this._g = mathRound(this._g); }
+ if (this._b < 1) { this._b = mathRound(this._b); }
+
+ this._ok = rgb.ok;
+ this._tc_id = tinyCounter++;
+ };
+
+ tinycolor.prototype = {
+ isDark: function() {
+ return this.getBrightness() < 128;
+ },
+ isLight: function() {
+ return !this.isDark();
+ },
+ isValid: function() {
+ return this._ok;
+ },
+ getOriginalInput: function() {
+ return this._originalInput;
+ },
+ getFormat: function() {
+ return this._format;
+ },
+ getAlpha: function() {
+ return this._a;
+ },
+ getBrightness: function() {
+ var rgb = this.toRgb();
+ return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
+ },
+ setAlpha: function(value) {
+ this._a = boundAlpha(value);
+ this._roundA = mathRound(100*this._a) / 100;
+ return this;
+ },
+ toHsv: function() {
+ var hsv = rgbToHsv(this._r, this._g, this._b);
+ return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a };
+ },
+ toHsvString: function() {
+ var hsv = rgbToHsv(this._r, this._g, this._b);
+ var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100);
+ return (this._a == 1) ?
+ "hsv(" + h + ", " + s + "%, " + v + "%)" :
+ "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")";
+ },
+ toHsl: function() {
+ var hsl = rgbToHsl(this._r, this._g, this._b);
+ return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a };
+ },
+ toHslString: function() {
+ var hsl = rgbToHsl(this._r, this._g, this._b);
+ var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100);
+ return (this._a == 1) ?
+ "hsl(" + h + ", " + s + "%, " + l + "%)" :
+ "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")";
+ },
+ toHex: function(allow3Char) {
+ return rgbToHex(this._r, this._g, this._b, allow3Char);
+ },
+ toHexString: function(allow3Char) {
+ return '#' + this.toHex(allow3Char);
+ },
+ toHex8: function() {
+ return rgbaToHex(this._r, this._g, this._b, this._a);
+ },
+ toHex8String: function() {
+ return '#' + this.toHex8();
+ },
+ toInteger: function() {
+ return Jimp.rgbaToInt(mathRound(this._r), mathRound(this._g), mathRound(this._b), this._a*255)
+ },
+ toRgb: function() {
+ return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a };
+ },
+ toRgbString: function() {
+ return (this._a == 1) ?
+ "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" :
+ "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")";
+ },
+ toPercentageRgb: function() {
+ return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a };
+ },
+ toPercentageRgbString: function() {
+ return (this._a == 1) ?
+ "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" :
+ "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")";
+ },
+ toName: function() {
+ if (this._a === 0) {
+ return "transparent";
+ }
+
+ if (this._a < 1) {
+ return false;
+ }
+
+ return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false;
+ },
+ toFilter: function(secondColor) {
+ var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a);
+ var secondHex8String = hex8String;
+ var gradientType = this._gradientType ? "GradientType = 1, " : "";
+
+ if (secondColor) {
+ var s = tinycolor(secondColor);
+ secondHex8String = s.toHex8String();
+ }
+
+ return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")";
+ },
+ toString: function(format) {
+ var formatSet = !!format;
+ format = format || this._format;
+
+ var formattedString = false;
+ var hasAlpha = this._a < 1 && this._a >= 0;
+ var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name");
+
+ if (needsAlphaFormat) {
+ // Special case for "transparent", all other non-alpha formats
+ // will return rgba when there is transparency.
+ if (format === "name" && this._a === 0) {
+ return this.toName();
+ }
+ return this.toRgbString();
+ }
+ if (format === "rgb") {
+ formattedString = this.toRgbString();
+ }
+ if (format === "prgb") {
+ formattedString = this.toPercentageRgbString();
+ }
+ if (format === "hex" || format === "hex6") {
+ formattedString = this.toHexString();
+ }
+ if (format === "hex3") {
+ formattedString = this.toHexString(true);
+ }
+ if (format === "hex8") {
+ formattedString = this.toHex8String();
+ }
+ if (format === "name") {
+ formattedString = this.toName();
+ }
+ if (format === "hsl") {
+ formattedString = this.toHslString();
+ }
+ if (format === "hsv") {
+ formattedString = this.toHsvString();
+ }
+
+ return formattedString || this.toHexString();
+ },
+
+ _applyModification: function(fn, args) {
+ var color = fn.apply(null, [this].concat([].slice.call(args)));
+ this._r = color._r;
+ this._g = color._g;
+ this._b = color._b;
+ this.setAlpha(color._a);
+ return this;
+ },
+ lighten: function() {
+ return this._applyModification(lighten, arguments);
+ },
+ brighten: function() {
+ return this._applyModification(brighten, arguments);
+ },
+ darken: function() {
+ return this._applyModification(darken, arguments);
+ },
+ desaturate: function() {
+ return this._applyModification(desaturate, arguments);
+ },
+ saturate: function() {
+ return this._applyModification(saturate, arguments);
+ },
+ greyscale: function() {
+ return this._applyModification(greyscale, arguments);
+ },
+ spin: function() {
+ return this._applyModification(spin, arguments);
+ },
+
+ _applyCombination: function(fn, args) {
+ return fn.apply(null, [this].concat([].slice.call(args)));
+ },
+ analogous: function() {
+ return this._applyCombination(analogous, arguments);
+ },
+ complement: function() {
+ return this._applyCombination(complement, arguments);
+ },
+ monochromatic: function() {
+ return this._applyCombination(monochromatic, arguments);
+ },
+ splitcomplement: function() {
+ return this._applyCombination(splitcomplement, arguments);
+ },
+ triad: function() {
+ return this._applyCombination(triad, arguments);
+ },
+ tetrad: function() {
+ return this._applyCombination(tetrad, arguments);
+ }
+ };
+
+ // If input is an object, force 1 into "1.0" to handle ratios properly
+ // String input requires "1.0" as input, so 1 will be treated as 1
+ tinycolor.fromRatio = function(color, opts) {
+ if (typeof color == "object") {
+ var newColor = {};
+ for (var i in color) {
+ if (color.hasOwnProperty(i)) {
+ if (i === "a") {
+ newColor[i] = color[i];
+ }
+ else {
+ newColor[i] = convertToPercentage(color[i]);
+ }
+ }
+ }
+ color = newColor;
+ }
+
+ return tinycolor(color, opts);
+ };
+
+ // Given a string or object, convert that input to RGB
+ // Possible string inputs:
+ //
+ // "red"
+ // "#f00" or "f00"
+ // "#ff0000" or "ff0000"
+ // "#ff000000" or "ff000000"
+ // "rgb 255 0 0" or "rgb (255, 0, 0)"
+ // "rgb 1.0 0 0" or "rgb (1, 0, 0)"
+ // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
+ // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
+ // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
+ // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
+ // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
+ //
+ function inputToRGB(color) {
+
+ var rgb = { r: 0, g: 0, b: 0 };
+ var a = 1;
+ var ok = false;
+ var format = false;
+
+ if (typeof color == "string") {
+ color = stringInputToObject(color);
+ }
+
+ if (typeof color == "object") {
+ if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) {
+ rgb = rgbToRgb(color.r, color.g, color.b);
+ ok = true;
+ format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb";
+ }
+ else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) {
+ color.s = convertToPercentage(color.s);
+ color.v = convertToPercentage(color.v);
+ rgb = hsvToRgb(color.h, color.s, color.v);
+ ok = true;
+ format = "hsv";
+ }
+ else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) {
+ color.s = convertToPercentage(color.s);
+ color.l = convertToPercentage(color.l);
+ rgb = hslToRgb(color.h, color.s, color.l);
+ ok = true;
+ format = "hsl";
+ }
+
+ if (color.hasOwnProperty("a")) {
+ a = color.a;
+ }
+ }
+
+ a = boundAlpha(a);
+
+ return {
+ ok: ok,
+ format: color.format || format,
+ r: mathMin(255, mathMax(rgb.r, 0)),
+ g: mathMin(255, mathMax(rgb.g, 0)),
+ b: mathMin(255, mathMax(rgb.b, 0)),
+ a: a
+ };
+ }
+
+
+ // Conversion Functions
+ // --------------------
+
+ // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
+ //
+
+ // `rgbToRgb`
+ // Handle bounds / percentage checking to conform to CSS color spec
+ //
+ // *Assumes:* r, g, b in [0, 255] or [0, 1]
+ // *Returns:* { r, g, b } in [0, 255]
+ function rgbToRgb(r, g, b){
+ return {
+ r: bound01(r, 255) * 255,
+ g: bound01(g, 255) * 255,
+ b: bound01(b, 255) * 255
+ };
+ }
+
+ // `rgbToHsl`
+ // Converts an RGB color value to HSL.
+ // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
+ // *Returns:* { h, s, l } in [0,1]
+ function rgbToHsl(r, g, b) {
+
+ r = bound01(r, 255);
+ g = bound01(g, 255);
+ b = bound01(b, 255);
+
+ var max = mathMax(r, g, b), min = mathMin(r, g, b);
+ var h, s, l = (max + min) / 2;
+
+ if(max == min) {
+ h = s = 0; // achromatic
+ }
+ else {
+ var d = max - min;
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ switch(max) {
+ case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+ case g: h = (b - r) / d + 2; break;
+ case b: h = (r - g) / d + 4; break;
+ }
+
+ h /= 6;
+ }
+
+ return { h: h, s: s, l: l };
+ }
+
+ // `hslToRgb`
+ // Converts an HSL color value to RGB.
+ // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
+ // *Returns:* { r, g, b } in the set [0, 255]
+ function hslToRgb(h, s, l) {
+ var r, g, b;
+
+ h = bound01(h, 360);
+ s = bound01(s, 100);
+ l = bound01(l, 100);
+
+ function hue2rgb(p, q, t) {
+ if(t < 0) t += 1;
+ if(t > 1) t -= 1;
+ if(t < 1/6) return p + (q - p) * 6 * t;
+ if(t < 1/2) return q;
+ if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
+ return p;
+ }
+
+ if(s === 0) {
+ r = g = b = l; // achromatic
+ }
+ else {
+ var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ var p = 2 * l - q;
+ r = hue2rgb(p, q, h + 1/3);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1/3);
+ }
+
+ return { r: r * 255, g: g * 255, b: b * 255 };
+ }
+
+ // `rgbToHsv`
+ // Converts an RGB color value to HSV
+ // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
+ // *Returns:* { h, s, v } in [0,1]
+ function rgbToHsv(r, g, b) {
+
+ r = bound01(r, 255);
+ g = bound01(g, 255);
+ b = bound01(b, 255);
+
+ var max = mathMax(r, g, b), min = mathMin(r, g, b);
+ var h, s, v = max;
+
+ var d = max - min;
+ s = max === 0 ? 0 : d / max;
+
+ if(max == min) {
+ h = 0; // achromatic
+ }
+ else {
+ switch(max) {
+ case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+ case g: h = (b - r) / d + 2; break;
+ case b: h = (r - g) / d + 4; break;
+ }
+ h /= 6;
+ }
+ return { h: h, s: s, v: v };
+ }
+
+ // `hsvToRgb`
+ // Converts an HSV color value to RGB.
+ // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
+ // *Returns:* { r, g, b } in the set [0, 255]
+ function hsvToRgb(h, s, v) {
+
+ h = bound01(h, 360) * 6;
+ s = bound01(s, 100);
+ v = bound01(v, 100);
+
+ var i = math.floor(h),
+ f = h - i,
+ p = v * (1 - s),
+ q = v * (1 - f * s),
+ t = v * (1 - (1 - f) * s),
+ mod = i % 6,
+ r = [v, q, p, p, t, v][mod],
+ g = [t, v, v, q, p, p][mod],
+ b = [p, p, t, v, v, q][mod];
+
+ return { r: r * 255, g: g * 255, b: b * 255 };
+ }
+
+ // `rgbToHex`
+ // Converts an RGB color to hex
+ // Assumes r, g, and b are contained in the set [0, 255]
+ // Returns a 3 or 6 character hex
+ function rgbToHex(r, g, b, allow3Char) {
+
+ var hex = [
+ pad2(mathRound(r).toString(16)),
+ pad2(mathRound(g).toString(16)),
+ pad2(mathRound(b).toString(16))
+ ];
+
+ // Return a 3 character hex if possible
+ if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
+ return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
+ }
+
+ return hex.join("");
+ }
+ // `rgbaToHex`
+ // Converts an RGBA color plus alpha transparency to hex
+ // Assumes r, g, b and a are contained in the set [0, 255]
+ // Returns an 8 character hex
+ function rgbaToHex(r, g, b, a) {
+
+ var hex = [
+ pad2(convertDecimalToHex(a)),
+ pad2(mathRound(r).toString(16)),
+ pad2(mathRound(g).toString(16)),
+ pad2(mathRound(b).toString(16))
+ ];
+
+ return hex.join("");
+ }
+
+ // `equals`
+ // Can be called with any tinycolor input
+ tinycolor.equals = function (color1, color2) {
+ if (!color1 || !color2) { return false; }
+ return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString();
+ };
+ tinycolor.random = function() {
+ return tinycolor.fromRatio({
+ r: mathRandom(),
+ g: mathRandom(),
+ b: mathRandom()
+ });
+ };
+
+
+ // Modification Functions
+ // ----------------------
+ // Thanks to less.js for some of the basics here
+ //
+
+ function desaturate(color, amount) {
+ amount = (amount === 0) ? 0 : (amount || 10);
+ var hsl = tinycolor(color).toHsl();
+ hsl.s -= amount / 100;
+ hsl.s = clamp01(hsl.s);
+ return tinycolor(hsl);
+ }
+
+ function saturate(color, amount) {
+ amount = (amount === 0) ? 0 : (amount || 10);
+ var hsl = tinycolor(color).toHsl();
+ hsl.s += amount / 100;
+ hsl.s = clamp01(hsl.s);
+ return tinycolor(hsl);
+ }
+
+ function greyscale(color) {
+ return tinycolor(color).desaturate(100);
+ }
+
+ function lighten (color, amount) {
+ amount = (amount === 0) ? 0 : (amount || 10);
+ var hsl = tinycolor(color).toHsl();
+ hsl.l += amount / 100;
+ hsl.l = clamp01(hsl.l);
+ return tinycolor(hsl);
+ }
+
+ function brighten(color, amount) {
+ amount = (amount === 0) ? 0 : (amount || 10);
+ var rgb = tinycolor(color).toRgb();
+ rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100))));
+ rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100))));
+ rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100))));
+ return tinycolor(rgb);
+ }
+
+ function darken (color, amount) {
+ amount = (amount === 0) ? 0 : (amount || 10);
+ var hsl = tinycolor(color).toHsl();
+ hsl.l -= amount / 100;
+ hsl.l = clamp01(hsl.l);
+ return tinycolor(hsl);
+ }
+
+ // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
+ // Values outside of this range will be wrapped into this range.
+ function spin(color, amount) {
+ var hsl = tinycolor(color).toHsl();
+ var hue = (mathRound(hsl.h) + amount) % 360;
+ hsl.h = hue < 0 ? 360 + hue : hue;
+ return tinycolor(hsl);
+ }
+
+ // Combination Functions
+ // ---------------------
+ // Thanks to jQuery xColor for some of the ideas behind these
+ //
+
+ function complement(color) {
+ var hsl = tinycolor(color).toHsl();
+ hsl.h = (hsl.h + 180) % 360;
+ return tinycolor(hsl);
+ }
+
+ function triad(color) {
+ var hsl = tinycolor(color).toHsl();
+ var h = hsl.h;
+ return [
+ tinycolor(color),
+ tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }),
+ tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l })
+ ];
+ }
+
+ function tetrad(color) {
+ var hsl = tinycolor(color).toHsl();
+ var h = hsl.h;
+ return [
+ tinycolor(color),
+ tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }),
+ tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }),
+ tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l })
+ ];
+ }
+
+ function splitcomplement(color) {
+ var hsl = tinycolor(color).toHsl();
+ var h = hsl.h;
+ return [
+ tinycolor(color),
+ tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}),
+ tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l})
+ ];
+ }
+
+ function analogous(color, results, slices) {
+ results = results || 6;
+ slices = slices || 30;
+
+ var hsl = tinycolor(color).toHsl();
+ var part = 360 / slices;
+ var ret = [tinycolor(color)];
+
+ for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) {
+ hsl.h = (hsl.h + part) % 360;
+ ret.push(tinycolor(hsl));
+ }
+ return ret;
+ }
+
+ function monochromatic(color, results) {
+ results = results || 6;
+ var hsv = tinycolor(color).toHsv();
+ var h = hsv.h, s = hsv.s, v = hsv.v;
+ var ret = [];
+ var modification = 1 / results;
+
+ while (results--) {
+ ret.push(tinycolor({ h: h, s: s, v: v}));
+ v = (v + modification) % 1;
+ }
+
+ return ret;
+ }
+
+ // Utility Functions
+ // ---------------------
+
+ tinycolor.mix = function(color1, color2, amount) {
+ amount = (amount === 0) ? 0 : (amount || 50);
+
+ var rgb1 = tinycolor(color1).toRgb();
+ var rgb2 = tinycolor(color2).toRgb();
+
+ var p = amount / 100;
+ var w = p * 2 - 1;
+ var a = rgb2.a - rgb1.a;
+
+ var w1;
+
+ if (w * a == -1) {
+ w1 = w;
+ } else {
+ w1 = (w + a) / (1 + w * a);
+ }
+
+ w1 = (w1 + 1) / 2;
+
+ var w2 = 1 - w1;
+
+ var rgba = {
+ r: rgb2.r * w1 + rgb1.r * w2,
+ g: rgb2.g * w1 + rgb1.g * w2,
+ b: rgb2.b * w1 + rgb1.b * w2,
+ a: rgb2.a * p + rgb1.a * (1 - p)
+ };
+
+ return tinycolor(rgba);
+ };
+
+
+ // Readability Functions
+ // ---------------------
+ //
+
+ // `readability`
+ // Analyze the 2 colors and returns an object with the following properties:
+ // `brightness`: difference in brightness between the two colors
+ // `color`: difference in color/hue between the two colors
+ tinycolor.readability = function(color1, color2) {
+ var c1 = tinycolor(color1);
+ var c2 = tinycolor(color2);
+ var rgb1 = c1.toRgb();
+ var rgb2 = c2.toRgb();
+ var brightnessA = c1.getBrightness();
+ var brightnessB = c2.getBrightness();
+ var colorDiff = (
+ Math.max(rgb1.r, rgb2.r) - Math.min(rgb1.r, rgb2.r) +
+ Math.max(rgb1.g, rgb2.g) - Math.min(rgb1.g, rgb2.g) +
+ Math.max(rgb1.b, rgb2.b) - Math.min(rgb1.b, rgb2.b)
+ );
+
+ return {
+ brightness: Math.abs(brightnessA - brightnessB),
+ color: colorDiff
+ };
+ };
+
+ // `readable`
+ // http://www.w3.org/TR/AERT#color-contrast
+ // Ensure that foreground and background color combinations provide sufficient contrast.
+ // *Example*
+ // tinycolor.isReadable("#000", "#111") => false
+ tinycolor.isReadable = function(color1, color2) {
+ var readability = tinycolor.readability(color1, color2);
+ return readability.brightness > 125 && readability.color > 500;
+ };
+
+ // `mostReadable`
+ // Given a base color and a list of possible foreground or background
+ // colors for that base, returns the most readable color.
+ // *Example*
+ // tinycolor.mostReadable("#123", ["#fff", "#000"]) => "#000"
+ tinycolor.mostReadable = function(baseColor, colorList) {
+ var bestColor = null;
+ var bestScore = 0;
+ var bestIsReadable = false;
+ for (var i=0; i < colorList.length; i++) {
+
+ // We normalize both around the "acceptable" breaking point,
+ // but rank brightness constrast higher than hue.
+
+ var readability = tinycolor.readability(baseColor, colorList[i]);
+ var readable = readability.brightness > 125 && readability.color > 500;
+ var score = 3 * (readability.brightness / 125) + (readability.color / 500);
+
+ if ((readable && ! bestIsReadable) ||
+ (readable && bestIsReadable && score > bestScore) ||
+ ((! readable) && (! bestIsReadable) && score > bestScore)) {
+ bestIsReadable = readable;
+ bestScore = score;
+ bestColor = tinycolor(colorList[i]);
+ }
+ }
+ return bestColor;
+ };
+
+
+ // Big List of Colors
+ // ------------------
+ //
+ var names = tinycolor.names = {
+ aliceblue: "f0f8ff",
+ antiquewhite: "faebd7",
+ aqua: "0ff",
+ aquamarine: "7fffd4",
+ azure: "f0ffff",
+ beige: "f5f5dc",
+ bisque: "ffe4c4",
+ black: "000",
+ blanchedalmond: "ffebcd",
+ blue: "00f",
+ blueviolet: "8a2be2",
+ brown: "a52a2a",
+ burlywood: "deb887",
+ burntsienna: "ea7e5d",
+ cadetblue: "5f9ea0",
+ chartreuse: "7fff00",
+ chocolate: "d2691e",
+ coral: "ff7f50",
+ cornflowerblue: "6495ed",
+ cornsilk: "fff8dc",
+ crimson: "dc143c",
+ cyan: "0ff",
+ darkblue: "00008b",
+ darkcyan: "008b8b",
+ darkgoldenrod: "b8860b",
+ darkgray: "a9a9a9",
+ darkgreen: "006400",
+ darkgrey: "a9a9a9",
+ darkkhaki: "bdb76b",
+ darkmagenta: "8b008b",
+ darkolivegreen: "556b2f",
+ darkorange: "ff8c00",
+ darkorchid: "9932cc",
+ darkred: "8b0000",
+ darksalmon: "e9967a",
+ darkseagreen: "8fbc8f",
+ darkslateblue: "483d8b",
+ darkslategray: "2f4f4f",
+ darkslategrey: "2f4f4f",
+ darkturquoise: "00ced1",
+ darkviolet: "9400d3",
+ deeppink: "ff1493",
+ deepskyblue: "00bfff",
+ dimgray: "696969",
+ dimgrey: "696969",
+ dodgerblue: "1e90ff",
+ firebrick: "b22222",
+ floralwhite: "fffaf0",
+ forestgreen: "228b22",
+ fuchsia: "f0f",
+ gainsboro: "dcdcdc",
+ ghostwhite: "f8f8ff",
+ gold: "ffd700",
+ goldenrod: "daa520",
+ gray: "808080",
+ green: "008000",
+ greenyellow: "adff2f",
+ grey: "808080",
+ honeydew: "f0fff0",
+ hotpink: "ff69b4",
+ indianred: "cd5c5c",
+ indigo: "4b0082",
+ ivory: "fffff0",
+ khaki: "f0e68c",
+ lavender: "e6e6fa",
+ lavenderblush: "fff0f5",
+ lawngreen: "7cfc00",
+ lemonchiffon: "fffacd",
+ lightblue: "add8e6",
+ lightcoral: "f08080",
+ lightcyan: "e0ffff",
+ lightgoldenrodyellow: "fafad2",
+ lightgray: "d3d3d3",
+ lightgreen: "90ee90",
+ lightgrey: "d3d3d3",
+ lightpink: "ffb6c1",
+ lightsalmon: "ffa07a",
+ lightseagreen: "20b2aa",
+ lightskyblue: "87cefa",
+ lightslategray: "789",
+ lightslategrey: "789",
+ lightsteelblue: "b0c4de",
+ lightyellow: "ffffe0",
+ lime: "0f0",
+ limegreen: "32cd32",
+ linen: "faf0e6",
+ magenta: "f0f",
+ maroon: "800000",
+ mediumaquamarine: "66cdaa",
+ mediumblue: "0000cd",
+ mediumorchid: "ba55d3",
+ mediumpurple: "9370db",
+ mediumseagreen: "3cb371",
+ mediumslateblue: "7b68ee",
+ mediumspringgreen: "00fa9a",
+ mediumturquoise: "48d1cc",
+ mediumvioletred: "c71585",
+ midnightblue: "191970",
+ mintcream: "f5fffa",
+ mistyrose: "ffe4e1",
+ moccasin: "ffe4b5",
+ navajowhite: "ffdead",
+ navy: "000080",
+ oldlace: "fdf5e6",
+ olive: "808000",
+ olivedrab: "6b8e23",
+ orange: "ffa500",
+ orangered: "ff4500",
+ orchid: "da70d6",
+ palegoldenrod: "eee8aa",
+ palegreen: "98fb98",
+ paleturquoise: "afeeee",
+ palevioletred: "db7093",
+ papayawhip: "ffefd5",
+ peachpuff: "ffdab9",
+ peru: "cd853f",
+ pink: "ffc0cb",
+ plum: "dda0dd",
+ powderblue: "b0e0e6",
+ purple: "800080",
+ rebeccapurple: "663399",
+ red: "f00",
+ rosybrown: "bc8f8f",
+ royalblue: "4169e1",
+ saddlebrown: "8b4513",
+ salmon: "fa8072",
+ sandybrown: "f4a460",
+ seagreen: "2e8b57",
+ seashell: "fff5ee",
+ sienna: "a0522d",
+ silver: "c0c0c0",
+ skyblue: "87ceeb",
+ slateblue: "6a5acd",
+ slategray: "708090",
+ slategrey: "708090",
+ snow: "fffafa",
+ springgreen: "00ff7f",
+ steelblue: "4682b4",
+ tan: "d2b48c",
+ teal: "008080",
+ thistle: "d8bfd8",
+ tomato: "ff6347",
+ turquoise: "40e0d0",
+ violet: "ee82ee",
+ wheat: "f5deb3",
+ white: "fff",
+ whitesmoke: "f5f5f5",
+ yellow: "ff0",
+ yellowgreen: "9acd32"
+ };
+
+ // Make it easy to access colors via `hexNames[hex]`
+ var hexNames = tinycolor.hexNames = flip(names);
+
+
+ // Utilities
+ // ---------
+
+ // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
+ function flip(o) {
+ var flipped = { };
+ for (var i in o) {
+ if (o.hasOwnProperty(i)) {
+ flipped[o[i]] = i;
+ }
+ }
+ return flipped;
+ }
+
+ // Return a valid alpha value [0,1] with all invalid values being set to 1
+ function boundAlpha(a) {
+ a = parseFloat(a);
+
+ if (isNaN(a) || a < 0 || a > 1) {
+ a = 1;
+ }
+
+ return a;
+ }
+
+ // Take input from [0, n] and return it as [0, 1]
+ function bound01(n, max) {
+ if (isOnePointZero(n)) { n = "100%"; }
+
+ var processPercent = isPercentage(n);
+ n = mathMin(max, mathMax(0, parseFloat(n)));
+
+ // Automatically convert percentage into number
+ if (processPercent) {
+ n = parseInt(n * max, 10) / 100;
+ }
+
+ // Handle floating point rounding errors
+ if ((math.abs(n - max) < 0.000001)) {
+ return 1;
+ }
+
+ // Convert into [0, 1] range if it isn't already
+ return (n % max) / parseFloat(max);
+ }
+
+ // Force a number between 0 and 1
+ function clamp01(val) {
+ return mathMin(1, mathMax(0, val));
+ }
+
+ // Parse a base-16 hex value into a base-10 integer
+ function parseIntFromHex(val) {
+ return parseInt(val, 16);
+ }
+
+ // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
+ //
+ function isOnePointZero(n) {
+ return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
+ }
+
+ // Check to see if string passed in is a percentage
+ function isPercentage(n) {
+ return typeof n === "string" && n.indexOf('%') != -1;
+ }
+
+ // Force a hex value to have 2 characters
+ function pad2(c) {
+ return c.length == 1 ? '0' + c : '' + c;
+ }
+
+ // Replace a decimal with it's percentage value
+ function convertToPercentage(n) {
+ if (n <= 1) {
+ n = (n * 100) + "%";
+ }
+
+ return n;
+ }
+
+ // Converts a decimal to a hex value
+ function convertDecimalToHex(d) {
+ return Math.round(parseFloat(d) * 255).toString(16);
+ }
+ // Converts a hex value to a decimal
+ function convertHexToDecimal(h) {
+ return (parseIntFromHex(h) / 255);
+ }
+
+ var matchers = (function() {
+
+ //
+ var CSS_INTEGER = "[-\\+]?\\d+%?";
+
+ //
+ var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";
+
+ // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
+ var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";
+
+ // Actual matching.
+ // Parentheses and commas are optional, but not required.
+ // Whitespace can take the place of commas or opening paren
+ var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
+ var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
+
+ return {
+ rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
+ rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
+ hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
+ hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
+ hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
+ hsva: new RegExp("hsva" + PERMISSIVE_MATCH4),
+ hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
+ hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
+ hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
+ };
+ })();
+
+ // `stringInputToObject`
+ // Permissive string parsing. Take in a number of formats, and output an object
+ // based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
+ function stringInputToObject(color) {
+
+ color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase();
+ var named = false;
+ if (names[color]) {
+ color = names[color];
+ named = true;
+ }
+ else if (color == 'transparent') {
+ return { r: 0, g: 0, b: 0, a: 0, format: "name" };
+ }
+
+ // Try to match string input using regular expressions.
+ // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
+ // Just return an object and let the conversion functions handle that.
+ // This way the result will be the same whether the tinycolor is initialized with string or object.
+ var match;
+ if ((match = matchers.rgb.exec(color))) {
+ return { r: match[1], g: match[2], b: match[3] };
+ }
+ if ((match = matchers.rgba.exec(color))) {
+ return { r: match[1], g: match[2], b: match[3], a: match[4] };
+ }
+ if ((match = matchers.hsl.exec(color))) {
+ return { h: match[1], s: match[2], l: match[3] };
+ }
+ if ((match = matchers.hsla.exec(color))) {
+ return { h: match[1], s: match[2], l: match[3], a: match[4] };
+ }
+ if ((match = matchers.hsv.exec(color))) {
+ return { h: match[1], s: match[2], v: match[3] };
+ }
+ if ((match = matchers.hsva.exec(color))) {
+ return { h: match[1], s: match[2], v: match[3], a: match[4] };
+ }
+ if ((match = matchers.hex8.exec(color))) {
+ return {
+ a: convertHexToDecimal(match[1]),
+ r: parseIntFromHex(match[2]),
+ g: parseIntFromHex(match[3]),
+ b: parseIntFromHex(match[4]),
+ format: named ? "name" : "hex8"
+ };
+ }
+ if ((match = matchers.hex6.exec(color))) {
+ return {
+ r: parseIntFromHex(match[1]),
+ g: parseIntFromHex(match[2]),
+ b: parseIntFromHex(match[3]),
+ format: named ? "name" : "hex"
+ };
+ }
+ if ((match = matchers.hex3.exec(color))) {
+ return {
+ r: parseIntFromHex(match[1] + '' + match[1]),
+ g: parseIntFromHex(match[2] + '' + match[2]),
+ b: parseIntFromHex(match[3] + '' + match[3]),
+ format: named ? "name" : "hex"
+ };
+ }
+
+ return false;
+ }
+
+ window.tinycolor = tinycolor;
+ })();
+
+ $(function () {
+ if ($.fn.spectrum.load) {
+ $.fn.spectrum.processNativeColorInputs();
+ }
+ });
});
diff --git a/lib/three_custom.js b/lib/three_custom.js
index 57618d9dd..34aba2a9d 100644
--- a/lib/three_custom.js
+++ b/lib/three_custom.js
@@ -69,17 +69,15 @@ THREE.Euler.prototype.inverse = function () {
};
}();
THREE.Vector3.prototype.removeEuler = function (euler) {
-
- var normal = new THREE.Vector3(0, 0, 1)
return function removeEuler(euler) {
-
- this.applyAxisAngle(normal, -euler.z)
- this.applyAxisAngle(normal.set(0, 1, 0), -euler.y)
- this.applyAxisAngle(normal.set(1, 0, 0), -euler.x)
+ var inverse = new THREE.Euler().copy(euler).inverse();
+ this.applyEuler(inverse)
return this;
-
};
}();
+THREE.Vector3.prototype.toString = function() {
+ return `${this.x}, ${this.y}, ${this.z}`
+}
var GridBox = function( from, to, size, material) {
var vertices = [];
diff --git a/package.json b/package.json
index 76fd219c8..d1379e0df 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "Blockbench",
"description": "Model editing and animation software",
- "version": "3.0.6",
+ "version": "3.1.0",
"license": "MIT",
"author": {
"name": "JannisX11",