11
22import { Store } from 'redux' ;
33import { ImageEditorTool , ImageEditorStore , TilemapState , AnimationState , CursorSize } from './store/imageReducer' ;
4- import { dispatchChangeZoom , dispatchUndoImageEdit , dispatchRedoImageEdit , dispatchChangeImageTool , dispatchSwapBackgroundForeground , dispatchChangeSelectedColor , dispatchImageEdit , dispatchChangeCursorSize } from './actions/dispatch' ;
4+ import { dispatchChangeZoom , dispatchUndoImageEdit , dispatchRedoImageEdit , dispatchChangeImageTool , dispatchSwapBackgroundForeground , dispatchChangeSelectedColor , dispatchImageEdit , dispatchChangeCursorSize , dispatchChangeCurrentFrame , dispatchSetFrames } from './actions/dispatch' ;
55import { mainStore } from './store/imageStore' ;
66import { EditState , flipEdit , getEditState , outlineEdit , replaceColorEdit , rotateEdit } from './toolDefinitions' ;
77let store = mainStore ;
@@ -60,6 +60,7 @@ function handleUndoRedo(event: KeyboardEvent) {
6060
6161function overrideBlocklyShortcuts ( event : KeyboardEvent ) {
6262 if ( event . key === "Backspace" || event . key === "Delete" ) {
63+ handleKeyDown ( event ) ;
6364 event . stopPropagation ( ) ;
6465 }
6566}
@@ -78,7 +79,7 @@ function handleKeyDown(event: KeyboardEvent) {
7879 case "e" :
7980 setTool ( ImageEditorTool . Erase ) ;
8081 break ;
81- case "h " :
82+ case "q " :
8283 setTool ( ImageEditorTool . Pan ) ;
8384 break ;
8485 case "b" :
@@ -111,34 +112,68 @@ function handleKeyDown(event: KeyboardEvent) {
111112 case "x" :
112113 swapForegroundBackground ( ) ;
113114 break ;
114- case "H " :
115+ case "h " :
115116 flip ( false ) ;
116117 break ;
117- case "V " :
118+ case "v " :
118119 flip ( true ) ;
119120 break ;
121+ case "H" :
122+ flipAllFrames ( false ) ;
123+ break ;
124+ case "V" :
125+ flipAllFrames ( true ) ;
126+ break ;
120127 case "[" :
121128 rotate ( false ) ;
122129 break ;
123130 case "]" :
124131 rotate ( true ) ;
125132 break ;
133+ case "{" :
134+ rotateAllFrames ( false ) ;
135+ break ;
136+ case "}" :
137+ rotateAllFrames ( true ) ;
138+ break ;
126139 case ">" :
127140 changeCursorSize ( true ) ;
128141 break ;
129142 case "<" :
130143 changeCursorSize ( false ) ;
131144 break ;
132-
145+ case "." :
146+ advanceFrame ( true ) ;
147+ break ;
148+ case "," :
149+ advanceFrame ( false ) ;
150+ break ;
151+ case "r" :
152+ doColorReplace ( ) ;
153+ break ;
154+ case "R" :
155+ doColorReplaceAllFrames ( ) ;
156+ break ;
157+ case "ArrowLeft" :
158+ moveMarqueeSelection ( - 1 , 0 , event . shiftKey ) ;
159+ break ;
160+ case "ArrowRight" :
161+ moveMarqueeSelection ( 1 , 0 , event . shiftKey ) ;
162+ break ;
163+ case "ArrowUp" :
164+ moveMarqueeSelection ( 0 , - 1 , event . shiftKey ) ;
165+ break ;
166+ case "ArrowDown" :
167+ moveMarqueeSelection ( 0 , 1 , event . shiftKey ) ;
168+ break ;
169+ case "Backspace" :
170+ case "Delete" :
171+ deleteSelection ( event . shiftKey ) ;
172+ break ;
133173 }
134174
135175 const editorState = store . getState ( ) . editor ;
136176
137- if ( event . shiftKey && event . code === "KeyR" ) {
138- replaceColor ( editorState . backgroundColor , editorState . selectedColor ) ;
139- return ;
140- }
141-
142177 if ( ! editorState . isTilemap && / ^ D i g i t \d $ / . test ( event . code ) ) {
143178 const keyAsNum = + event . code . slice ( - 1 ) ;
144179 const color = keyAsNum + ( event . shiftKey ? 9 : 0 ) ;
@@ -216,12 +251,20 @@ export function flip(vertical: boolean) {
216251 dispatchAction ( dispatchImageEdit ( flipped . toImageState ( ) ) ) ;
217252}
218253
254+ export function flipAllFrames ( vertical : boolean ) {
255+ editAllFrames ( ( ) => flip ( vertical ) , editState => flipEdit ( editState , vertical , false ) ) ;
256+ }
257+
219258export function rotate ( clockwise : boolean ) {
220259 const [ editState , type ] = currentEditState ( ) ;
221260 const rotated = rotateEdit ( editState , clockwise , type === "tilemap" , type === "animation" ) ;
222261 dispatchAction ( dispatchImageEdit ( rotated . toImageState ( ) ) ) ;
223262}
224263
264+ export function rotateAllFrames ( clockwise : boolean ) {
265+ editAllFrames ( ( ) => rotate ( clockwise ) , editState => rotateEdit ( editState , clockwise , false , true ) ) ;
266+ }
267+
225268export function outline ( color : number ) {
226269 const [ editState , type ] = currentEditState ( ) ;
227270
@@ -235,4 +278,142 @@ export function replaceColor(fromColor: number, toColor: number) {
235278 const [ editState , type ] = currentEditState ( ) ;
236279 const replaced = replaceColorEdit ( editState , fromColor , toColor ) ;
237280 dispatchAction ( dispatchImageEdit ( replaced . toImageState ( ) ) ) ;
281+ }
282+
283+ function doColorReplace ( ) {
284+ const state = store . getState ( ) ;
285+
286+ const fromColor = state . editor . backgroundColor ;
287+ const toColor = state . editor . selectedColor ;
288+
289+ const [ editState ] = currentEditState ( ) ;
290+ const replaced = replaceColorEdit ( editState , fromColor , toColor ) ;
291+ dispatchAction ( dispatchImageEdit ( replaced . toImageState ( ) ) ) ;
292+ }
293+
294+ function doColorReplaceAllFrames ( ) {
295+ const state = store . getState ( ) ;
296+
297+ const fromColor = state . editor . backgroundColor ;
298+ const toColor = state . editor . selectedColor ;
299+
300+ editAllFrames ( doColorReplace , editState => replaceColorEdit ( editState , fromColor , toColor ) )
301+ }
302+
303+ export function advanceFrame ( forwards : boolean ) {
304+ const state = store . getState ( ) ;
305+
306+ if ( state . editor . isTilemap ) return ;
307+
308+ const present = state . store . present as AnimationState ;
309+
310+ if ( present . frames . length <= 1 ) return ;
311+
312+ let nextFrame : number ;
313+ if ( forwards ) {
314+ nextFrame = ( present . currentFrame + 1 ) % present . frames . length ;
315+ }
316+ else {
317+ nextFrame = ( present . currentFrame + present . frames . length - 1 ) % present . frames . length ;
318+ }
319+
320+ dispatchAction ( dispatchChangeCurrentFrame ( nextFrame ) ) ;
321+ }
322+
323+ function editAllFrames ( singleFrameShortcut : ( ) => void , doEdit : ( editState : EditState ) => EditState ) {
324+ const state = store . getState ( ) ;
325+
326+ if ( state . editor . isTilemap ) {
327+ singleFrameShortcut ( ) ;
328+ return ;
329+ }
330+
331+ const present = state . store . present as AnimationState ;
332+
333+ if ( present . frames . length === 1 ) {
334+ singleFrameShortcut ( ) ;
335+ return ;
336+ }
337+
338+ const current = present . frames [ present . currentFrame ] ;
339+
340+ // if the current frame has a marquee selection, apply that selection
341+ // to all frames
342+ const hasFloatingLayer = ! ! current . floating ;
343+ const layerWidth = current . floating ?. bitmap . width ;
344+ const layerHeight = current . floating ?. bitmap . height ;
345+ const layerX = current . layerOffsetX ;
346+ const layerY = current . layerOffsetY ;
347+
348+ const newFrames : pxt . sprite . ImageState [ ] = [ ] ;
349+
350+ for ( const frame of present . frames ) {
351+ const editState = getEditState ( frame , false ) ;
352+
353+ if ( hasFloatingLayer ) {
354+ if ( editState . floating ?. image ) {
355+ // check the existing floating layer to see if it matches before
356+ // merging down. otherwise non-square rotations might lose
357+ // information if they cause the floating layer to go off the canvas
358+ if ( editState . layerOffsetX !== layerX ||
359+ editState . layerOffsetY !== layerY ||
360+ editState . floating . image . width !== layerWidth ||
361+ editState . floating . image . height !== layerHeight
362+ ) {
363+ editState . mergeFloatingLayer ( ) ;
364+ editState . copyToLayer ( layerX , layerY , layerWidth , layerHeight , true ) ;
365+ }
366+ }
367+ else {
368+ editState . copyToLayer ( layerX , layerY , layerWidth , layerHeight , true ) ;
369+ }
370+ }
371+ else {
372+ editState . mergeFloatingLayer ( ) ;
373+ }
374+
375+ const edited = doEdit ( editState ) ;
376+ newFrames . push ( edited . toImageState ( ) ) ;
377+ }
378+
379+ dispatchAction ( dispatchSetFrames ( newFrames , present . currentFrame ) ) ;
380+ }
381+
382+ function moveMarqueeSelection ( dx : number , dy : number , allFrames = false ) {
383+ const [ editState ] = currentEditState ( ) ;
384+
385+ if ( ! editState . floating ?. image ) return ;
386+
387+ const moveState = ( editState : EditState ) => {
388+ editState . layerOffsetX += dx ;
389+ editState . layerOffsetY += dy ;
390+ return editState ;
391+ } ;
392+
393+
394+ if ( ! allFrames ) {
395+ dispatchAction ( dispatchImageEdit ( moveState ( editState ) . toImageState ( ) ) ) ;
396+ }
397+ else {
398+ editAllFrames ( ( ) => moveMarqueeSelection ( dx , dy ) , moveState ) ;
399+ }
400+ }
401+
402+ function deleteSelection ( allFrames = false ) {
403+ const [ editState ] = currentEditState ( ) ;
404+
405+ if ( ! editState . floating ?. image ) return ;
406+
407+ const deleteFloatingLayer = ( editState : EditState ) => {
408+ editState . floating = null ;
409+ return editState ;
410+ } ;
411+
412+
413+ if ( ! allFrames ) {
414+ dispatchAction ( dispatchImageEdit ( deleteFloatingLayer ( editState ) . toImageState ( ) ) ) ;
415+ }
416+ else {
417+ editAllFrames ( ( ) => deleteSelection ( ) , deleteFloatingLayer ) ;
418+ }
238419}
0 commit comments