├── README.md └── WindowComponent.coffee /README.md: -------------------------------------------------------------------------------- 1 | # WindowComponent 2 | 3 | Allows for quick and easy creation of Mac and Browser windows. Ideal for making responsive designs. 4 | 5 | Note: be sure not to use `window` and `document` as variable names. These names are reserved and will break your prototype. 6 | 7 | ## Example 8 | 9 | - [Basic Setup](http://share.framerjs.com/cvnfqelnpkto/) 10 | - [Mac Desktop](http://share.framerjs.com/pehxi6fey0eo/) 11 | 12 | ## Properties 13 | 14 | - **`resizable`** *\* 15 | - **`minWidth`** *\* 16 | - **`minHeight`** *\* 17 | - **`toolbarHeight`** *\* 18 | - **`topConstraint`** *\* 19 | 20 | ```coffee 21 | {WindowComponent} = require "WindowComponent" 22 | 23 | docWindow = new WindowComponent 24 | ``` 25 | 26 | ## Events 27 | 28 | - **`onResize`** (window, content *\*) 29 | 30 | ```coffee 31 | docWindow.onResize (window, content) -> 32 | # update layout 33 | ``` 34 | -------------------------------------------------------------------------------- /WindowComponent.coffee: -------------------------------------------------------------------------------- 1 | """ 2 | WindowComponent 3 | 4 | resizable 5 | minWidth 6 | minHeight 7 | toolbarHeight 8 | topConstraint 9 | 10 | onResize (window, content) -> 11 | """ 12 | 13 | Utils.domComplete -> document.body.style.cursor = "auto" 14 | 15 | class DocumentWindowConstants 16 | @onResize = "onResize" 17 | 18 | class exports.WindowComponent extends Layer 19 | 20 | constructor: (options) -> 21 | super _.defaults options, 22 | x: Align.center 23 | y: Align.center 24 | width: 400 25 | height: 540 26 | minWidth: 200 27 | minHeight: 200 28 | toolbarHeight: 36 29 | resizable: true 30 | backgroundColor: null 31 | shadowY: 10 32 | shadowBlur: 25 33 | borderRadius: 5 34 | shadowColor: "rgba(0,0,0,0.5)" 35 | topConstraint: 0 36 | 37 | @content = new Layer 38 | name: "content" 39 | parent: @ 40 | backgroundColor: "white" 41 | clip: true 42 | 43 | @toolbar = new Layer 44 | name: "toolbar" 45 | parent: @ 46 | shadowY: 1, shadowColor: "rgba(0,0,0,0.25)" 47 | style: background: "linear-gradient(#FFF, #DDD)" 48 | 49 | @title = new Layer 50 | height: 36 51 | name: "title" 52 | html: @name 53 | parent: @toolbar 54 | backgroundColor: null 55 | color: "black" 56 | style: 57 | fontSize: "13px" 58 | textAlign:"center" 59 | lineHeight:"36px" 60 | 61 | windowButton = (buttonName, buttonColor, xPos) => 62 | new Layer 63 | x: xPos 64 | y: 12 65 | size: 12 66 | borderRadius: 6 67 | borderWidth: 1 68 | borderColor: "rgba(0,0,0,0.2)" 69 | parent: @toolbar 70 | name: buttonName 71 | backgroundColor: buttonColor 72 | @close = windowButton("close", "FF504F", 12) 73 | @minimize = windowButton("minimize", "FFB900", 32) 74 | @maximize = windowButton("maximize", "00CD16", 52) 75 | 76 | @_makeDraggable() 77 | @_makeResizable() 78 | 79 | @_layout() 80 | @onChange "borderRadius", @_layout 81 | 82 | @onChange "size", => 83 | @_layout() 84 | @emit(DocumentWindowConstants.onResize, @, @content) 85 | 86 | _layout: => 87 | @y = Math.max(@topConstraint, @y) if @topConstraint? 88 | @width = Math.max(@minWidth, @width) if @minWidth? 89 | @height = Math.max(@minHeight, @height) if @minHeight? 90 | @toolbar?.style.borderRadius = "#{@borderRadius}px #{@borderRadius}px 0 0" 91 | @content?.style.borderRadius = "0 0 #{@borderRadius}px #{@borderRadius}px" 92 | @content?.y = @toolbarHeight 93 | @content?.width = @width 94 | @content?.height = @height - @toolbarHeight 95 | @toolbar?.width = @width 96 | @toolbar?.height = @toolbarHeight 97 | @title?.width = @width 98 | 99 | margin = 10 100 | @_resizeLayers?.forEach (l) => 101 | offsetY = 0 102 | offsetY = -margin / 2 if l.resizingIndex < 3 103 | offsetY = @height - margin / 2 if l.resizingIndex > 5 104 | offsetX = 0 105 | offsetX = -margin / 2 if l.resizingIndex % 3 is 0 106 | offsetX = @width - margin / 2 if (l.resizingIndex - 2) % 3 is 0 107 | width = margin 108 | width = @width if (l.resizingIndex - 1) % 3 is 0 109 | height = margin 110 | height = @height if Math.floor(l.resizingIndex / 3) is 1 111 | l.x = offsetX 112 | l.y = offsetY 113 | l.width = width 114 | l.height = height 115 | l.bringToFront() if l.resizingIndex % 2 is 0 116 | 117 | _makeDraggable: => 118 | @toolbar.onPanStart (e) => 119 | @_state = "auto" 120 | @_windowStartPoint = @point 121 | @_panStartPoint = Canvas.convertPointToScreen({x: e.pageX, y: e.pageY}) 122 | @toolbar.onPanEnd => 123 | @_state = null 124 | @pixelAlign() 125 | @toolbar.onPan (e) => 126 | panPoint = Canvas.convertPointToScreen({x: e.pageX, y: e.pageY}) 127 | panOffset = Utils.pointSubtract(@_panStartPoint, panPoint) 128 | windowPoint = Utils.pointSubtract(@_windowStartPoint, panOffset) 129 | windowPoint.y = Math.max(windowPoint.y, @topConstraint) 130 | @point = windowPoint 131 | 132 | _makeResizable: => 133 | cursors = ["nwse-resize", "ns-resize", "nesw-resize", "ew-resize", "", "ew-resize", "nesw-resize", "ns-resize", "nwse-resize"] 134 | minSizeCursors = ["nw-resize", "n-resize", "ne-resize", "w-resize", "", "e-resize", "sw-resize", "s-resize", "se-resize"] 135 | @_resizeLayers = [] 136 | 137 | for resizingIndex in [0...9] 138 | continue if resizingIndex is 4 139 | 140 | resizeLayer = new Layer 141 | parent: @ 142 | name: ".resizingHandle" 143 | backgroundColor: null 144 | resizeLayer.resizingIndex = resizingIndex 145 | cursorIndex = resizingIndex 146 | resizeLayer.custom = 147 | cursor: cursors[cursorIndex] 148 | minCursor: minSizeCursors[cursorIndex] 149 | @_resizeLayers.push(resizeLayer) 150 | 151 | do (resizeLayer) => 152 | 153 | resizeLayer.onMouseMove (e) => 154 | return unless @resizable 155 | return if @_state? 156 | document.body.style.cursor = resizeLayer.custom.cursor 157 | 158 | corner = resizeLayer.resizingIndex % 2 is 0 159 | left = resizeLayer.resizingIndex % 3 is 0 160 | right = (resizeLayer.resizingIndex - 2) % 3 is 0 161 | top = resizeLayer.resizingIndex < 3 162 | bottom = resizeLayer.resizingIndex > 5 163 | 164 | if corner 165 | document.body.style.cursor = resizeLayer.custom.minCursor if @width is @minWidth and @height is @minHeight 166 | else if left or right 167 | document.body.style.cursor = resizeLayer.custom.minCursor if @width is @minWidth 168 | else if right or bottom 169 | document.body.style.cursor = resizeLayer.custom.minCursor if @height is @minHeight 170 | 171 | resizeLayer.onMouseOut (e) => 172 | return unless @resizable 173 | document.body.style.cursor = "auto" unless @_state? 174 | 175 | resizeLayer.onPanStart (e) => 176 | return unless @resizable 177 | @_state = resizeLayer.custom.cursor 178 | document.body.style.cursor = @_state 179 | @_windowStartPoint = @point 180 | @_panStartPoint = Canvas.convertPointToScreen({x: e.pageX, y: e.pageY}) 181 | @_windowStartSize = @size 182 | 183 | resizeLayer.onPanEnd (e) => 184 | return unless @resizable 185 | @_state = null 186 | document.body.style.cursor = "auto" 187 | 188 | resizeLayer.onPan (e) => 189 | return unless @resizable 190 | panPoint = Canvas.convertPointToScreen({x: e.pageX, y: e.pageY}) 191 | panOffset = Utils.pointSubtract(@_panStartPoint, panPoint) 192 | 193 | maxX = @maxX - @minWidth 194 | maxY = @maxY - @minHeight 195 | 196 | corner = resizeLayer.resizingIndex % 2 is 0 197 | left = resizeLayer.resizingIndex % 3 is 0 198 | right = (resizeLayer.resizingIndex - 2) % 3 is 0 199 | top = resizeLayer.resizingIndex < 3 200 | bottom = resizeLayer.resizingIndex > 5 201 | 202 | document.body.style.cursor = resizeLayer.custom.cursor 203 | 204 | if left 205 | @x = Math.min(maxX, @_windowStartPoint.x - panOffset.x) 206 | @width = Math.max(@_windowStartSize.width + panOffset.x, @minWidth) 207 | document.body.style.cursor = resizeLayer.custom.minCursor if @width is @minWidth and not corner 208 | if right 209 | @width = Math.max(@_windowStartSize.width - panOffset.x, @minWidth) 210 | document.body.style.cursor = resizeLayer.custom.minCursor if @width is @minWidth and not corner 211 | if top 212 | maxHeight = @.maxY - @topConstraint 213 | @y = Math.max(@topConstraint, Math.min(maxY, @_windowStartPoint.y - panOffset.y)) 214 | @height = Math.min(maxHeight, Math.max(@_windowStartSize.height + panOffset.y, @minHeight)) 215 | document.body.style.cursor = resizeLayer.custom.minCursor if @height is @minHeight and not corner 216 | if bottom 217 | @height = Math.max(@_windowStartSize.height - panOffset.y, @minHeight) 218 | document.body.style.cursor = resizeLayer.custom.minCursor if @height is @minHeight and not corner 219 | if corner 220 | document.body.style.cursor = resizeLayer.custom.minCursor if @height is @minHeight and @width is @minWidth 221 | 222 | @define "resizable", 223 | get: -> return @_resizable 224 | set: (value) -> 225 | return unless _.isBoolean(value) 226 | @_resizable = value 227 | 228 | @define "toolbarHeight", 229 | get: -> return @_toolbarHeight 230 | set: (value) -> 231 | return unless _.isNumber(value) 232 | @_toolbarHeight = value 233 | @_layout() 234 | 235 | @define "minWidth", 236 | get: -> return @_minWidth 237 | set: (value) -> 238 | return unless _.isNumber(value) 239 | @_minWidth = value 240 | @width = value if @width < value 241 | 242 | @define "minHeight", 243 | get: -> return @_minHeight 244 | set: (value) -> 245 | return unless _.isNumber(value) 246 | @_minHeight = value 247 | @height = value if @height < value 248 | 249 | @define "topConstraint", 250 | get: -> return @_topConstraint 251 | set: (value) -> 252 | return unless _.isNumber(value) 253 | @_topConstraint = value 254 | @_layout() 255 | 256 | ############################################################## 257 | ## EVENTS 258 | 259 | onResize: (cb) -> @on(DocumentWindowConstants.onResize, cb) 260 | 261 | --------------------------------------------------------------------------------