mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 09:35:39 -05:00
Built the thang editor colors tab.
This commit is contained in:
parent
2d5ca50e89
commit
1a3c6498e8
10 changed files with 347 additions and 9 deletions
|
@ -1,9 +1,13 @@
|
|||
{hexToHSL, hslToHex} = require 'lib/utils'
|
||||
|
||||
module.exports = class SpriteBuilder
|
||||
constructor: (@thangType, @options) ->
|
||||
@options ?= {}
|
||||
raw = _.cloneDeep(@thangType.get('raw'))
|
||||
@shapeStore = raw.shapes
|
||||
@containerStore = raw.containers
|
||||
@animationStore = raw.animations
|
||||
@buildColorMaps()
|
||||
|
||||
setOptions: (@options) ->
|
||||
|
||||
|
@ -93,7 +97,7 @@ module.exports = class SpriteBuilder
|
|||
if shapeData.lf?
|
||||
shape.graphics.lf shapeData.lf...
|
||||
else if shapeData.fc?
|
||||
shape.graphics.f shapeData.fc
|
||||
shape.graphics.f @colorMap[shapeKey] or shapeData.fc
|
||||
if shapeData.ls?
|
||||
shape.graphics.ls shapeData.ls...
|
||||
else if shapeData.sc?
|
||||
|
@ -118,3 +122,64 @@ module.exports = class SpriteBuilder
|
|||
cont.addChild(child)
|
||||
cont.bounds = new createjs.Rectangle(contData.b...)
|
||||
cont
|
||||
|
||||
buildColorMaps: ->
|
||||
@colorMap = {}
|
||||
colorGroups = @thangType.get('colorGroups')
|
||||
return if _.isEmpty colorGroups
|
||||
colorConfig = @options.colorConfig
|
||||
# colorConfig ?= {team: {hue:0.4, saturation: -0.5, lightness: -0.5}} # test config
|
||||
return if not colorConfig
|
||||
|
||||
for group, config of colorConfig
|
||||
continue unless colorGroups[group] # color group not found...
|
||||
@buildColorMapForGroup(colorGroups[group], config)
|
||||
|
||||
buildColorMapForGroup: (shapes, config) ->
|
||||
return unless shapes.length
|
||||
colors = @initColorMap(shapes)
|
||||
@adjustHuesForColorMap(colors, config.hue)
|
||||
@adjustColorMap(colors, 1, config.lightness)
|
||||
@adjustColorMap(colors, 2, config.saturation)
|
||||
@applyColorMap(shapes, colors)
|
||||
|
||||
initColorMap: (shapes) ->
|
||||
colors = {}
|
||||
for shapeKey in shapes
|
||||
shape = @shapeStore[shapeKey]
|
||||
continue if (not shape.fc?) or colors[shape.fc]
|
||||
hsl = hexToHSL(shape.fc)
|
||||
colors[shape.fc] = hsl
|
||||
colors
|
||||
|
||||
adjustHuesForColorMap: (colors, targetHue) ->
|
||||
hues = (hsl[0] for hex, hsl of colors)
|
||||
|
||||
# 'rotate' the hue spectrum so averaging works
|
||||
if Math.max(hues) - Math.min(hues) > 0.5
|
||||
hues = (if h < 0.5 then h + 1.0 else h for h in hues)
|
||||
averageHue = sum(hues) / hues.length
|
||||
averageHue %= 1
|
||||
# end result should be something like a hue array of [0.9, 0.3] gets an average of 0.1
|
||||
|
||||
targetHue ?= 0
|
||||
diff = targetHue - averageHue
|
||||
hsl[0] = (hsl[0] + diff + 1) % 1 for hex, hsl of colors
|
||||
|
||||
adjustColorMap: (colorMap, index, adjustment) ->
|
||||
return unless adjustment
|
||||
for hex, hsl of colorMap
|
||||
value = hsl[index]
|
||||
if adjustment > 0
|
||||
hsl[index] = value + (1 - value) * adjustment
|
||||
else
|
||||
hsl[index] = value + value * adjustment
|
||||
|
||||
applyColorMap: (shapes, colors) ->
|
||||
for shapeKey in shapes
|
||||
shape = @shapeStore[shapeKey]
|
||||
continue if (not shape.fc?) or not(colors[shape.fc])
|
||||
@colorMap[shapeKey] = hslToHex(colors[shape.fc])
|
||||
|
||||
|
||||
sum = (nums) -> _.reduce(nums, (s, num) -> s + num)
|
|
@ -28,4 +28,20 @@ module.exports.normalizeFunc = (func_thing, object) ->
|
|||
console.error("Could not find method", func_thing, 'in object', @)
|
||||
return => null # always return a func, or Mediator will go boom
|
||||
func_thing = func
|
||||
return func_thing
|
||||
return func_thing
|
||||
|
||||
module.exports.hexToHSL = (hex) ->
|
||||
rgbToHsl(hexToR(hex), hexToG(hex), hexToB(hex))
|
||||
|
||||
hexToR = (h) -> parseInt (cutHex(h)).substring(0, 2), 16
|
||||
hexToG = (h) -> parseInt (cutHex(h)).substring(2, 4), 16
|
||||
hexToB = (h) -> parseInt (cutHex(h)).substring(4, 6), 16
|
||||
cutHex = (h) -> (if (h.charAt(0) is "#") then h.substring(1, 7) else h)
|
||||
|
||||
module.exports.hslToHex = (hsl) ->
|
||||
'#' + (toHex(n) for n in hslToRgb(hsl...)).join('')
|
||||
|
||||
toHex = (n) ->
|
||||
h = Math.floor(n).toString(16)
|
||||
h = '0'+h if h.length is 1
|
||||
h
|
55
app/styles/editor/thang/colors_tab.sass
Normal file
55
app/styles/editor/thang/colors_tab.sass
Normal file
|
@ -0,0 +1,55 @@
|
|||
$height: 550px
|
||||
|
||||
#editor-thang-colors-tab-view
|
||||
#color-group-settings
|
||||
width: 75%
|
||||
box-sizing: border-box
|
||||
float: right
|
||||
|
||||
#color-groups-treema
|
||||
float: left
|
||||
width: 25%
|
||||
height: $height
|
||||
box-sizing: border-box
|
||||
margin-bottom: 20px
|
||||
|
||||
#shape-buttons
|
||||
border: 1px solid saddlebrown
|
||||
margin: 0 20px 0 10px
|
||||
width: 30%
|
||||
float: left
|
||||
height: $height
|
||||
overflow: scroll
|
||||
background: sandybrown
|
||||
padding: 5px
|
||||
box-sizing: border-box
|
||||
|
||||
button
|
||||
width: 20px
|
||||
height: 25px
|
||||
border: 2px solid white
|
||||
margin: 1px
|
||||
|
||||
&.selected
|
||||
border: 2px solid black
|
||||
|
||||
#controls
|
||||
//clear: both
|
||||
float: left
|
||||
width: 60%
|
||||
text-align: center
|
||||
background-color: aliceblue
|
||||
border: 1px solid blue
|
||||
|
||||
.slider-cell
|
||||
margin: 20px 10px
|
||||
border-color: black
|
||||
.ui-slider
|
||||
border: 1px solid black
|
||||
.ui-slider-handle
|
||||
border: 1px solid black
|
||||
|
||||
canvas
|
||||
width: 60%
|
||||
background: steelblue
|
||||
border: 1px solid darkblue
|
22
app/templates/editor/thang/colors_tab.jade
Normal file
22
app/templates/editor/thang/colors_tab.jade
Normal file
|
@ -0,0 +1,22 @@
|
|||
div#color-groups-treema
|
||||
|
||||
div#color-group-settings.hide
|
||||
div#shape-buttons
|
||||
|
||||
canvas#tinting-display(width=400, height=400)
|
||||
|
||||
div#controls
|
||||
div.slider-cell
|
||||
| Hue:
|
||||
span.hue-label
|
||||
.selector#hue-slider
|
||||
|
||||
div.slider-cell
|
||||
| Saturation:
|
||||
span.saturation-label
|
||||
.selector#saturation-slider
|
||||
|
||||
div.slider-cell
|
||||
| Lightness:
|
||||
span.lightness-label
|
||||
.selector#lightness-slider
|
|
@ -16,8 +16,12 @@ block content
|
|||
a(href="#editor-thang-components-tab-view", data-toggle="tab") Components
|
||||
li
|
||||
a(href="#editor-thang-canvases-view", data-toggle="tab") Canvases
|
||||
li
|
||||
a(href="#editor-thang-colors-tab-view", data-toggle="tab")#color-tab Colors
|
||||
|
||||
div.tab-content
|
||||
div.tab-pane#editor-thang-colors-tab-view
|
||||
|
||||
div.tab-pane.active#editor-thang-main-tab-view
|
||||
|
||||
div.main-area.well
|
||||
|
|
164
app/views/editor/thang/colors_tab_view.coffee
Normal file
164
app/views/editor/thang/colors_tab_view.coffee
Normal file
|
@ -0,0 +1,164 @@
|
|||
CocoView = require 'views/kinds/CocoView'
|
||||
template = require 'templates/editor/thang/colors_tab'
|
||||
SpriteBuilder = require 'lib/sprites/SpriteBuilder'
|
||||
{hexToHSL} = require 'lib/utils'
|
||||
|
||||
module.exports = class ColorsTabView extends CocoView
|
||||
id: 'editor-thang-colors-tab-view'
|
||||
template: template
|
||||
className: 'tab-pane'
|
||||
|
||||
offset: 0
|
||||
|
||||
constructor: (@thangType, options) ->
|
||||
@thangType.once 'sync', @tryToBuild, @
|
||||
@thangType.schema().once 'sync', @tryToBuild, @
|
||||
@colorConfig = { hue: 0, saturation: 0, lightness: 0 }
|
||||
@spriteBuilder = new SpriteBuilder(@thangType)
|
||||
f = =>
|
||||
@offset++
|
||||
@updateMovieClip()
|
||||
@interval = setInterval f, 1000
|
||||
super options
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@createShapeButtons()
|
||||
@initStage()
|
||||
@initSliders()
|
||||
@tryToBuild()
|
||||
|
||||
# sliders
|
||||
|
||||
initSliders: ->
|
||||
@hueSlider = @initSlider $("#hue-slider", @$el), 0, @updateHue
|
||||
@saturationSlider = @initSlider $("#saturation-slider", @$el), 50, @updateSaturation
|
||||
@lightnessSlider = @initSlider $("#lightness-slider", @$el), 50, @updateLightness
|
||||
|
||||
updateHue: =>
|
||||
@colorConfig.hue = @hueSlider.slider('value') / 100
|
||||
@updateMovieClip()
|
||||
|
||||
updateSaturation: =>
|
||||
@colorConfig.saturation = (@saturationSlider.slider('value') / 50) - 1
|
||||
@updateMovieClip()
|
||||
|
||||
updateLightness: =>
|
||||
@colorConfig.lightness = (@lightnessSlider.slider('value') / 50) - 1
|
||||
@updateMovieClip()
|
||||
|
||||
# movie clip
|
||||
|
||||
initStage: ->
|
||||
canvas = @$el.find('#tinting-display')
|
||||
@stage = new createjs.Stage(canvas[0])
|
||||
createjs.Ticker.setFPS 20
|
||||
createjs.Ticker.addEventListener("tick", @stage)
|
||||
@updateMovieClip()
|
||||
|
||||
updateMovieClip: ->
|
||||
return unless @currentColorGroupTreema
|
||||
actionDict = @thangType.getActions()
|
||||
animations = (a.animation for key, a of actionDict when a.animation)
|
||||
index = @offset % animations.length
|
||||
animation = animations[index]
|
||||
return unless animation
|
||||
@stage.removeChild(@movieClip) if @movieClip
|
||||
options = {colorConfig: {}}
|
||||
options.colorConfig[@currentColorGroupTreema.keyForParent] = @colorConfig
|
||||
console.log 'options are', options
|
||||
@spriteBuilder.setOptions options
|
||||
@spriteBuilder.buildColorMaps()
|
||||
@movieClip = @spriteBuilder.buildMovieClip animation
|
||||
larger = Math.min(400 / @movieClip.nominalBounds.width, 400 / @movieClip.nominalBounds.height)
|
||||
@movieClip.scaleX = larger
|
||||
@movieClip.scaleY = larger
|
||||
@movieClip.regX = @movieClip.nominalBounds.x
|
||||
@movieClip.regY = @movieClip.nominalBounds.y
|
||||
console.log 'added movie clip', @movieClip
|
||||
@stage.addChild @movieClip
|
||||
|
||||
createShapeButtons: ->
|
||||
buttons = $('<div></div>').prop('id', 'shape-buttons')
|
||||
shapes = (shape for key, shape of @thangType.get('raw')?.shapes or {})
|
||||
colors = (s.fc for s in shapes when s.fc?)
|
||||
colors = _.uniq(colors)
|
||||
colors.sort (a, b) ->
|
||||
aHSL = hexToHSL(a)
|
||||
bHSL = hexToHSL(b)
|
||||
if aHSL[0] > bHSL[0] then -1 else 1
|
||||
|
||||
for color in colors
|
||||
button = $('<button></button>').addClass('btn')
|
||||
button.css('background', color)
|
||||
button.val color
|
||||
buttons.append(button)
|
||||
buttons.click (e) =>
|
||||
$(e.target).toggleClass('selected')
|
||||
@updateColorGroup()
|
||||
@$el.find('#shape-buttons').replaceWith(buttons)
|
||||
console.log 'making buttons'
|
||||
@buttons = buttons
|
||||
|
||||
tryToBuild: ->
|
||||
return unless @thangType.loaded and @thangType.schema().loaded
|
||||
data = @thangType.get('colorGroups')
|
||||
data ?= {}
|
||||
schema = @thangType.schema().attributes.properties?.colorGroups
|
||||
treemaOptions =
|
||||
data: data
|
||||
schema: schema
|
||||
callbacks:
|
||||
change: @onColorGroupsChanged
|
||||
select: @onColorGroupSelected
|
||||
nodeClasses:
|
||||
'thang-color-group': ColorGroupNode
|
||||
@colorGroups = @$el.find('#color-groups-treema').treema treemaOptions
|
||||
@colorGroups.build()
|
||||
@colorGroups.open()
|
||||
keys = Object.keys @colorGroups.childrenTreemas
|
||||
console.log 'selected?'
|
||||
@colorGroups.childrenTreemas[keys[0]]?.$el.click() if keys[0]
|
||||
|
||||
onColorGroupsChanged: =>
|
||||
@thangType.set('colorGroups', @colorGroups.data)
|
||||
|
||||
onColorGroupSelected: (e, selected) =>
|
||||
console.log 'selected!'
|
||||
@$el.find('#color-group-settings').toggleClass('hide', not selected.length)
|
||||
treema = @colorGroups.getLastSelectedTreema()
|
||||
return unless treema
|
||||
@currentColorGroupTreema = treema
|
||||
|
||||
shapes = {}
|
||||
shapes[shape] = true for shape in treema.data
|
||||
|
||||
colors = {}
|
||||
for key, shape of @thangType.get('raw')?.shapes or {}
|
||||
continue unless shape.fc?
|
||||
colors[shape.fc] = true if shapes[key]
|
||||
|
||||
console.log 'buttons?', @buttons
|
||||
@buttons.find('button').removeClass('selected')
|
||||
@buttons.find('button').each (i, button) ->
|
||||
$(button).addClass('selected') if colors[$(button).val()]
|
||||
|
||||
@updateMovieClip()
|
||||
|
||||
updateColorGroup: ->
|
||||
colors = {}
|
||||
@buttons.find('button').each (i, button) ->
|
||||
return unless $(button).hasClass('selected')
|
||||
window.button = button
|
||||
colors[$(button).val()] = true
|
||||
|
||||
shapes = []
|
||||
for key, shape of @thangType.get('raw')?.shapes or {}
|
||||
continue unless shape.fc?
|
||||
shapes.push(key) if colors[shape.fc]
|
||||
|
||||
@currentColorGroupTreema.set('/', shapes)
|
||||
|
||||
class ColorGroupNode extends TreemaNode.nodeMap.array
|
||||
collection: false
|
||||
canAddChild: -> false
|
|
@ -8,6 +8,8 @@ Camera = require 'lib/surface/Camera'
|
|||
ThangComponentEditView = require 'views/editor/components/main'
|
||||
DocumentFiles = require 'collections/DocumentFiles'
|
||||
|
||||
ColorsTabView = require './colors_tab_view'
|
||||
|
||||
CENTER = {x:200, y:300}
|
||||
|
||||
module.exports = class ThangTypeEditView extends View
|
||||
|
@ -66,6 +68,7 @@ module.exports = class ThangTypeEditView extends View
|
|||
@buildTreema()
|
||||
@initSliders()
|
||||
@initComponents()
|
||||
@insertSubView(new ColorsTabView(@thangType))
|
||||
|
||||
initComponents: =>
|
||||
options =
|
||||
|
@ -243,13 +246,6 @@ module.exports = class ThangTypeEditView extends View
|
|||
|
||||
# sliders
|
||||
|
||||
initSlider: ($el, startValue, changeCallback) ->
|
||||
slider = $el.slider({ animate: "fast" })
|
||||
slider.slider('value', startValue)
|
||||
slider.on('slide',changeCallback)
|
||||
slider.on('slidechange',changeCallback)
|
||||
slider
|
||||
|
||||
initSliders: ->
|
||||
@rotationSlider = @initSlider $("#rotation-slider", @$el), 50, @updateRotation
|
||||
@scaleSlider = @initSlider $('#scale-slider', @$el), 29, @updateScale
|
||||
|
|
|
@ -217,5 +217,14 @@ module.exports = class CocoView extends Backbone.View
|
|||
ua = navigator.userAgent or navigator.vendor or window.opera
|
||||
return ua.search("MSIE") != -1
|
||||
|
||||
initSlider: ($el, startValue, changeCallback) ->
|
||||
slider = $el.slider({ animate: "fast" })
|
||||
slider.slider('value', startValue)
|
||||
slider.on('slide',changeCallback)
|
||||
slider.on('slidechange',changeCallback)
|
||||
slider
|
||||
|
||||
|
||||
|
||||
mobileRELong = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i
|
||||
mobileREShort = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i
|
||||
|
|
|
@ -19,6 +19,7 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
|
|||
'positions',
|
||||
'snap',
|
||||
'components'
|
||||
'colorGroups'
|
||||
]
|
||||
|
||||
hasAccess: (req) ->
|
||||
|
|
|
@ -122,6 +122,12 @@ _.extend ThangTypeSchema.properties,
|
|||
title: 'Scale'
|
||||
type: 'number'
|
||||
positions: PositionsSchema
|
||||
colorGroups: c.object
|
||||
title: 'Color Groups'
|
||||
additionalProperties:
|
||||
type:'array'
|
||||
format: 'thang-color-group'
|
||||
items: {type:'string'}
|
||||
snap: c.object { title: "Snap", description: "In the level editor, snap positioning to these intervals.", required: ['x', 'y'] },
|
||||
x:
|
||||
title: "Snap X"
|
||||
|
|
Loading…
Reference in a new issue