Built the thang editor colors tab.

This commit is contained in:
Scott Erickson 2014-01-10 16:48:28 -08:00
parent 2d5ca50e89
commit 1a3c6498e8
10 changed files with 347 additions and 9 deletions

View file

@ -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)

View file

@ -29,3 +29,19 @@ module.exports.normalizeFunc = (func_thing, object) ->
return => null # always return a func, or Mediator will go boom
func_thing = func
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

View 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

View 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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View file

@ -19,6 +19,7 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
'positions',
'snap',
'components'
'colorGroups'
]
hasAccess: (req) ->

View file

@ -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"