2022-11-24 15:30:13 +00:00
const selectTransformTool = ( ) =>
toolbar . registerTool (
2022-12-18 23:50:40 +00:00
"./res/icons/box-select.svg" ,
2022-11-24 15:30:13 +00:00
"Select Image" ,
( state , opt ) => {
// Draw new cursor immediately
2022-12-17 03:55:53 +00:00
ovLayer . clear ( ) ;
2022-11-29 20:55:25 +00:00
state . movecb ( mouse . coords . world . pos ) ;
2022-11-24 15:30:13 +00:00
2022-11-25 18:22:16 +00:00
// Canvas left mouse handlers
2022-11-29 20:55:25 +00:00
mouse . listen . world . onmousemove . on ( state . movecb ) ;
mouse . listen . world . btn . left . onclick . on ( state . clickcb ) ;
mouse . listen . world . btn . left . ondragstart . on ( state . dragstartcb ) ;
2023-01-11 03:36:14 +00:00
mouse . listen . world . btn . left . ondrag . on ( state . dragcb ) ;
2022-11-29 20:55:25 +00:00
mouse . listen . world . btn . left . ondragend . on ( state . dragendcb ) ;
2024-08-26 02:28:36 +00:00
2024-08-26 13:25:22 +00:00
mouse . listen . world . btn . left . ondclick . on ( state . dclickcb ) ;
mouse . listen . world . btn . right . ondclick . on ( state . drclickcb ) ;
2022-11-24 15:30:13 +00:00
2022-11-25 18:22:16 +00:00
// Canvas right mouse handler
2022-11-29 20:55:25 +00:00
mouse . listen . world . btn . right . onclick . on ( state . cancelcb ) ;
2022-11-25 03:34:34 +00:00
2022-11-25 18:22:16 +00:00
// Keyboard click handlers
2022-11-25 03:34:34 +00:00
keyboard . listen . onkeyclick . on ( state . keyclickcb ) ;
keyboard . listen . onkeydown . on ( state . keydowncb ) ;
2022-11-25 18:22:16 +00:00
2022-12-21 15:07:29 +00:00
// Layer system handlers
uil . onactive . on ( state . uilayeractivecb ) ;
2024-08-25 20:59:35 +00:00
2024-08-26 02:28:36 +00:00
// Undo
2024-08-25 20:59:35 +00:00
commands . onundo . on ( state . undocb ) ;
commands . onredo . on ( state . redocb ) ;
2022-12-21 15:07:29 +00:00
2022-11-25 18:22:16 +00:00
// Registers keyboard shortcuts
2023-01-17 02:20:13 +00:00
keyboard . onShortcut ( { ctrl : true , key : "KeyA" } , state . ctrlacb ) ;
keyboard . onShortcut (
{ ctrl : true , shift : true , key : "KeyA" } ,
state . ctrlsacb
) ;
2022-11-25 03:34:34 +00:00
keyboard . onShortcut ( { ctrl : true , key : "KeyC" } , state . ctrlccb ) ;
keyboard . onShortcut ( { ctrl : true , key : "KeyV" } , state . ctrlvcb ) ;
keyboard . onShortcut ( { ctrl : true , key : "KeyX" } , state . ctrlxcb ) ;
2023-04-09 02:05:22 +00:00
keyboard . onShortcut ( { key : "Equal" } , state . togglemirror ) ;
2024-08-25 22:24:28 +00:00
keyboard . onShortcut ( { key : "Enter" } , state . entercb ) ;
keyboard . onShortcut ( { shift : true , key : "Enter" } , state . sentercb ) ;
keyboard . onShortcut ( { ctrl : true , key : "Enter" } , state . ctentercb ) ;
2024-08-26 02:28:36 +00:00
keyboard . onShortcut ( { ctrl : true , shift : true , key : "Enter" } , state . sctentercb ) ;
2024-08-25 22:24:28 +00:00
keyboard . onShortcut ( { key : "Delete" } , state . delcb ) ;
keyboard . onShortcut ( { shift : true , key : "Delete" } , state . sdelcb ) ;
keyboard . onShortcut ( { key : "Escape" } , state . escapecb ) ;
2022-11-25 16:16:22 +00:00
2023-04-09 02:05:22 +00:00
state . ctxmenu . mirrorSelectionCheckbox . disabled = true ;
2022-11-25 16:16:22 +00:00
state . selected = null ;
2023-01-12 04:00:06 +00:00
// Register Layer
state . originalDisplayLayer = imageCollection . registerLayer ( null , {
after : uil . layer ,
category : "select-display" ,
} ) ;
2022-11-24 15:30:13 +00:00
} ,
( state , opt ) => {
2022-11-25 18:22:16 +00:00
// Clear all those listeners and shortcuts we set up
2022-11-29 20:55:25 +00:00
mouse . listen . world . onmousemove . clear ( state . movecb ) ;
mouse . listen . world . btn . left . onclick . clear ( state . clickcb ) ;
mouse . listen . world . btn . left . ondragstart . clear ( state . dragstartcb ) ;
2023-01-11 03:36:14 +00:00
mouse . listen . world . btn . left . ondrag . clear ( state . dragcb ) ;
2022-11-29 20:55:25 +00:00
mouse . listen . world . btn . left . ondragend . clear ( state . dragendcb ) ;
2024-08-26 02:28:36 +00:00
mouse . listen . world . btn . left . ondclick . clear ( state . dclickcb ) ;
2024-08-26 13:25:22 +00:00
mouse . listen . world . btn . right . ondclick . clear ( state . drclickcb ) ;
2022-11-24 15:30:13 +00:00
2022-11-29 20:55:25 +00:00
mouse . listen . world . btn . right . onclick . clear ( state . cancelcb ) ;
2022-11-25 03:34:34 +00:00
keyboard . listen . onkeyclick . clear ( state . keyclickcb ) ;
keyboard . listen . onkeydown . clear ( state . keydowncb ) ;
2024-08-26 02:28:36 +00:00
2023-01-17 02:20:13 +00:00
keyboard . deleteShortcut ( state . ctrlacb , "KeyA" ) ;
keyboard . deleteShortcut ( state . ctrlsacb , "KeyA" ) ;
2022-11-25 03:34:34 +00:00
keyboard . deleteShortcut ( state . ctrlccb , "KeyC" ) ;
keyboard . deleteShortcut ( state . ctrlvcb , "KeyV" ) ;
keyboard . deleteShortcut ( state . ctrlxcb , "KeyX" ) ;
2023-04-09 02:05:22 +00:00
keyboard . deleteShortcut ( state . togglemirror , "Equal" ) ;
2024-08-25 22:24:28 +00:00
keyboard . deleteShortcut ( state . entercb , "Enter" ) ;
keyboard . deleteShortcut ( state . sentercb , "Enter" ) ;
keyboard . deleteShortcut ( state . ctentercb , "Enter" ) ;
2024-08-26 02:28:36 +00:00
keyboard . deleteShortcut ( state . sctentercb , "Enter" ) ;
2024-08-25 22:24:28 +00:00
keyboard . deleteShortcut ( state . delcb , "Delete" ) ;
keyboard . deleteShortcut ( state . sdelcb , "Delete" ) ;
keyboard . deleteShortcut ( state . escapecb , "Escape" ) ;
2022-12-21 15:07:29 +00:00
uil . onactive . clear ( state . uilayeractivecb ) ;
2024-08-25 20:59:35 +00:00
commands . onundo . clear ( state . undocb ) ;
commands . onredo . clear ( state . redocb ) ;
2022-11-25 18:22:16 +00:00
// Clear any selections
2022-11-25 03:34:34 +00:00
state . reset ( ) ;
2022-11-25 18:22:16 +00:00
// Resets cursor
2022-12-17 03:55:53 +00:00
ovLayer . clear ( ) ;
2022-12-06 15:25:06 +00:00
// Clears overlay
2022-11-29 20:55:25 +00:00
imageCollection . inputElement . style . cursor = "auto" ;
2023-01-12 04:00:06 +00:00
// Delete Layer
imageCollection . deleteLayer ( state . originalDisplayLayer ) ;
state . originalDisplayLayer = null ;
2022-11-24 15:30:13 +00:00
} ,
{
init : ( state ) => {
2022-11-25 03:34:34 +00:00
state . clipboard = { } ;
2022-11-24 15:30:13 +00:00
state . snapToGrid = true ;
2022-11-25 03:34:34 +00:00
state . keepAspectRatio = true ;
2022-12-19 02:35:48 +00:00
state . block _res _change = true ;
2024-08-25 22:24:28 +00:00
state . toNewLayer = false ;
2022-12-03 14:40:40 +00:00
state . useClipboard = ! ! (
navigator . clipboard && navigator . clipboard . write
) ; // Use it by default if supported
2022-12-21 15:07:29 +00:00
state . selectionPeekOpacity = 40 ;
2022-11-25 03:34:34 +00:00
state . original = null ;
2022-11-25 16:16:22 +00:00
state . _selected = null ;
Object . defineProperty ( state , "selected" , {
get : ( ) => state . _selected ,
set : ( v ) => {
if ( v ) state . ctxmenu . enableButtons ( ) ;
else state . ctxmenu . disableButtons ( ) ;
return ( state . _selected = v ) ;
} ,
} ) ;
2022-11-25 03:34:34 +00:00
2022-11-25 18:22:16 +00:00
// Some things to easy request for a redraw
2022-11-25 03:34:34 +00:00
state . lastMouseTarget = null ;
2022-12-21 15:07:29 +00:00
state . lastMouseMove = { x : 0 , y : 0 } ;
2022-11-25 03:34:34 +00:00
2022-12-06 15:25:06 +00:00
state . redraw = ( ) => {
2022-12-17 03:55:53 +00:00
ovLayer . clear ( ) ;
2022-11-25 03:34:34 +00:00
state . movecb ( state . lastMouseMove ) ;
} ;
2022-12-21 15:07:29 +00:00
state . uilayeractivecb = ( { uilayer } ) => {
if ( state . originalDisplayLayer ) {
state . originalDisplayLayer . moveAfter ( uilayer . layer ) ;
}
} ;
2023-01-13 00:49:06 +00:00
/** @type {{selected: Point, offset: Point} | null} */
let moving = null ;
/** @type {{handle: Point} | null} */
let scaling = null ;
let rotating = false ;
2022-11-25 18:22:16 +00:00
// Clears selection and make things right
2022-12-21 15:07:29 +00:00
state . reset = ( erase = false ) => {
if ( state . selected && ! erase )
2023-01-12 04:00:06 +00:00
state . original . layer . ctx . drawImage (
state . selected . canvas ,
2022-11-25 18:22:16 +00:00
state . original . x ,
state . original . y
2022-11-25 03:59:10 +00:00
) ;
2022-12-21 15:07:29 +00:00
if ( state . originalDisplayLayer ) {
2023-01-12 04:00:06 +00:00
state . originalDisplayLayer . clear ( ) ;
2022-12-21 15:07:29 +00:00
}
2022-11-25 03:34:34 +00:00
if ( state . dragging ) state . dragging = null ;
2023-04-09 02:05:22 +00:00
else {
state . ctxmenu . mirrorSelectionCheckbox . disabled = true ;
state . selected = null ;
}
2022-11-25 03:34:34 +00:00
2023-04-09 02:05:22 +00:00
state . mirrorSetValue ( false ) ;
2023-01-08 04:54:37 +00:00
state . rotation = 0 ;
state . original = null ;
2023-01-13 00:49:06 +00:00
moving = null ;
scaling = null ;
rotating = null ;
2023-01-08 04:54:37 +00:00
2022-12-06 15:25:06 +00:00
state . redraw ( ) ;
2022-11-25 03:34:34 +00:00
} ;
2022-11-24 15:30:13 +00:00
2023-01-08 21:21:08 +00:00
// Selection Handlers
const selection = _tool . _draggable _selection ( state ) ;
2022-11-24 15:30:13 +00:00
2023-01-12 04:00:06 +00:00
// UI Erasers
2023-01-11 03:36:14 +00:00
let eraseSelectedBox = ( ) => null ;
2023-01-12 04:00:06 +00:00
let eraseSelectedImage = ( ) => null ;
2023-01-08 21:21:08 +00:00
let eraseCursor = ( ) => null ;
2023-01-11 03:36:14 +00:00
let eraseSelection = ( ) => null ;
2023-01-12 04:00:06 +00:00
// Redraw UI
state . redrawui = ( ) => {
// Get cursor positions
const { x , y , sx , sy } = _tool . _process _cursor (
state . lastMouseMove ,
state . snapToGrid
) ;
eraseSelectedBox ( ) ;
if ( state . selected ) {
eraseSelectedBox = state . selected . drawBox (
uiCtx ,
{ x , y } ,
viewport . c2v
) ;
}
} ;
2024-08-25 20:59:35 +00:00
// Undo/Redo Handling, reset state before Undo/Redo
state . undocb = ( undo ) => {
if ( state . selected ) {
2024-08-26 13:25:22 +00:00
// Cancel so undo shortcut effectively undoes the current transform, unless requesting multiple steps
if ( state . selectionTransformed ( ) && undo . n <= 1 )
undo . cancel ( ) ;
2024-08-25 20:59:35 +00:00
state . reset ( false ) ;
}
2024-08-26 13:25:22 +00:00
}
2024-08-25 20:59:35 +00:00
state . redocb = ( redo ) => {
if ( state . selected ) { state . reset ( false ) ; }
}
2023-01-12 04:00:06 +00:00
2023-04-09 02:05:22 +00:00
// Mirroring
state . togglemirror = ( ) => {
state . mirrorSetValue ( ! state . mirrorSelection ) ;
} ;
2023-01-12 04:00:06 +00:00
// Mouse Move Handler
2022-11-24 15:30:13 +00:00
state . movecb = ( evn ) => {
2023-01-12 04:00:06 +00:00
state . lastMouseMove = evn ;
2023-01-08 21:21:08 +00:00
// Get cursor positions
const { x , y , sx , sy } = _tool . _process _cursor ( evn , state . snapToGrid ) ;
2022-12-06 15:25:06 +00:00
2023-01-11 03:36:14 +00:00
// Erase Cursor
eraseSelectedBox ( ) ;
2023-01-12 04:00:06 +00:00
eraseSelectedImage ( ) ;
2023-01-11 03:36:14 +00:00
eraseSelection ( ) ;
2023-01-08 21:21:08 +00:00
eraseCursor ( ) ;
2023-01-11 03:36:14 +00:00
imageCollection . inputElement . style . cursor = "default" ;
2023-01-08 04:54:37 +00:00
2023-01-08 21:21:08 +00:00
// Draw Box and Selected Image
if ( state . selected ) {
2023-01-12 04:00:06 +00:00
eraseSelectedBox = state . selected . drawBox (
uiCtx ,
{ x , y } ,
viewport . c2v
) ;
if (
state . selected . hoveringBox ( x , y ) ||
2023-01-13 00:49:06 +00:00
state . selected . hoveringHandle ( x , y , viewport . zoom ) . onHandle ||
state . selected . hoveringRotateHandle ( x , y , viewport . zoom )
2023-01-12 04:00:06 +00:00
) {
imageCollection . inputElement . style . cursor = "pointer" ;
}
eraseSelectedImage = state . selected . drawImage (
state . originalDisplayLayer . ctx ,
ovCtx ,
{ opacity : state . selectionPeekOpacity / 100 }
) ;
2022-11-29 20:55:25 +00:00
}
2022-11-24 15:30:13 +00:00
2023-01-08 21:21:08 +00:00
// Draw Selection
if ( selection . exists ) {
2023-01-11 03:36:14 +00:00
uiCtx . save ( ) ;
2022-12-06 15:25:06 +00:00
uiCtx . setLineDash ( [ 2 , 2 ] ) ;
2023-01-11 03:36:14 +00:00
uiCtx . lineWidth = 2 ;
2022-12-06 15:25:06 +00:00
uiCtx . strokeStyle = "#FFF" ;
2022-11-24 15:30:13 +00:00
2023-01-11 03:36:14 +00:00
const bbvp = selection . bb . transform ( viewport . c2v ) ;
uiCtx . beginPath ( ) ;
uiCtx . strokeRect ( bbvp . x , bbvp . y , bbvp . w , bbvp . h ) ;
uiCtx . stroke ( ) ;
eraseSelection = ( ) =>
uiCtx . clearRect (
bbvp . x - 10 ,
bbvp . y - 10 ,
bbvp . w + 20 ,
bbvp . h + 20
) ;
uiCtx . restore ( ) ;
}
// Draw cursor
eraseCursor = _tool . _cursor _draw ( sx , sy ) ;
2022-11-24 15:30:13 +00:00
} ;
2022-11-25 18:22:16 +00:00
// Handles left mouse clicks
2023-01-12 04:00:06 +00:00
state . clickcb = ( evn ) => {
2024-08-26 13:25:22 +00:00
if ( state . selectionTransformed ( ) ) {
2024-08-25 22:24:28 +00:00
state . applyTransform ( ) ;
2023-01-12 23:32:53 +00:00
} else {
state . reset ( ) ;
2023-01-12 04:00:06 +00:00
}
} ;
2024-08-26 13:25:22 +00:00
// Check if selection has been transformed in any way.
state . selectionTransformed = ( ) => {
return state . selected &&
! (
state . selected . rotation === 0 &&
state . selected . scale . x === 1 &&
state . selected . scale . y === 1 &&
state . selected . position . x === state . original . sx &&
state . selected . position . y === state . original . sy &&
! state . mirrorSelection &&
state . original . layer === uil . layer
) ;
}
2022-11-25 18:22:16 +00:00
2024-08-26 13:25:22 +00:00
// Handles left mouse double clicks - Select All Ctrl-A
// Holding shift key - Ctrl-Shift-A
2024-08-26 02:28:36 +00:00
state . dclickcb = ( evn ) => {
2024-08-26 13:25:22 +00:00
// Do nothing if Ctrl Key is held for panning
if ( state . selected || evn . evn . ctrlKey ) return ;
let shift = evn . evn . shiftKey ;
2024-08-26 02:28:36 +00:00
// Wait so clickcb doesn't immediately deselect.
state . dclickcb _timeout = state . dclickcb _timeout ? ? window . setTimeout ( async ( ) => {
state . dclickcb _timeout = null ;
if ( ! state . selected && ! selection . exists ) {
2024-08-26 13:25:22 +00:00
if ( shift ) state . ctrlsacb ( evn ) ;
else state . ctrlacb ( evn ) ;
}
2024-08-26 02:28:36 +00:00
} , 300 ) ;
} ;
2024-08-26 13:25:22 +00:00
// Handles right mouse double clicks - Select topmost layer with content under pointer
2024-08-26 14:44:37 +00:00
// Holding shift key Selects the next topmost if current layer has visible content under pointer.
2024-08-26 13:25:22 +00:00
state . drclickcb = ( evn ) => {
if ( state . selected ) return ;
2024-08-26 14:44:37 +00:00
// If shift key is held, and current layer is has visible pixels under pointer
// select topmost visible layer beneath the active layer
let shift = evn . evn . shiftKey
&& ! uil . active . hidden
&& ! isCanvasBlank ( evn . x , evn . y , 2 , 2 , uil . active . canvas ) ;
let layer = shift ? uil . active : null ;
for ( let l of uil . layers . toReversed ( ) ) {
if ( shift ) {
if ( layer == l ) shift = false ;
}
else if ( ! l . hidden && ! isCanvasBlank ( evn . x , evn . y , 2 , 2 , l . canvas ) ) {
layer = l ;
break ;
2024-08-26 13:25:22 +00:00
}
2024-08-26 14:44:37 +00:00
}
if ( layer ) {
uil . active = layer ;
state . dclickcb _timeout = state . dclickcb _timeout ? ? window . setTimeout ( async ( ) => {
state . dclickcb _timeout = null ;
if ( ! state . selected && ! selection . exists ) { state . ctrlacb ( evn ) ; }
} , 300 ) ;
2024-08-26 13:25:22 +00:00
}
} ;
2023-01-11 03:36:14 +00:00
// Handles left mouse drag start events
2022-11-24 15:30:13 +00:00
state . dragstartcb = ( evn ) => {
2023-01-12 04:00:06 +00:00
const {
x : ix ,
y : iy ,
sx : six ,
sy : siy ,
} = _tool . _process _cursor ( { x : evn . ix , y : evn . iy } , state . snapToGrid ) ;
const { x , y , sx , sy } = _tool . _process _cursor ( evn , state . snapToGrid ) ;
if ( state . selected ) {
const hoveringBox = state . selected . hoveringBox ( ix , iy ) ;
2023-01-13 00:49:06 +00:00
const hoveringHandle = state . selected . hoveringHandle (
ix ,
iy ,
viewport . zoom
) ;
const hoveringRotateHandle = state . selected . hoveringRotateHandle (
ix ,
iy ,
viewport . zoom
) ;
2023-01-12 04:00:06 +00:00
if ( hoveringBox ) {
// Start dragging
moving = {
selected : state . selected . position ,
offset : {
x : six - state . selected . position . x ,
y : siy - state . selected . position . y ,
} ,
} ;
return ;
} else if ( hoveringHandle . onHandle ) {
// Start scaling
let handle = { x : 0 , y : 0 } ;
const lbb = new BoundingBox ( {
x : - state . selected . canvas . width / 2 ,
y : - state . selected . canvas . height / 2 ,
w : state . selected . canvas . width ,
h : state . selected . canvas . height ,
} ) ;
if ( hoveringHandle . ontl ) {
handle = lbb . tl ;
} else if ( hoveringHandle . ontr ) {
handle = lbb . tr ;
} else if ( hoveringHandle . onbl ) {
handle = lbb . bl ;
} else {
handle = lbb . br ;
}
scaling = {
handle ,
} ;
return ;
2023-01-13 00:49:06 +00:00
} else if ( hoveringRotateHandle ) {
rotating = true ;
return ;
2023-01-12 04:00:06 +00:00
}
}
2023-01-11 03:36:14 +00:00
selection . dragstartcb ( evn ) ;
} ;
2023-01-13 00:49:06 +00:00
const transform = ( evn , x , y , sx , sy ) => {
if ( moving ) {
state . selected . position = {
x : sx - moving . offset . x ,
y : sy - moving . offset . y ,
} ;
}
2023-01-12 04:00:06 +00:00
2023-01-13 00:49:06 +00:00
if ( scaling ) {
/** @type {DOMMatrix} */
const m = state . selected . rtmatrix . invertSelf ( ) ;
const lscursor = m . transformPoint ( { x : sx , y : sy } ) ;
const xs = lscursor . x / scaling . handle . x ;
2023-12-15 16:27:44 +00:00
const ys = lscursor . y / scaling . handle . y ;
2023-01-13 00:49:06 +00:00
2023-04-09 02:05:22 +00:00
let xscale = 1 ;
let yscale = 1 ;
if ( ! state . keepAspectRatio ) {
xscale = xs ;
yscale = ys ;
} else {
2023-12-15 16:27:44 +00:00
xscale = yscale = Math . max ( xs , ys ) ;
2023-01-12 04:00:06 +00:00
}
2023-04-09 02:05:22 +00:00
state . selected . scale = { x : xscale , y : yscale } ;
2023-01-13 00:49:06 +00:00
}
2023-01-12 04:00:06 +00:00
2023-01-13 00:49:06 +00:00
if ( rotating ) {
const center = state . selected . matrix . transformPoint ( { x : 0 , y : 0 } ) ;
let angle = Math . atan2 ( x - center . x , center . y - y ) ;
2023-01-12 04:00:06 +00:00
2023-01-13 00:49:06 +00:00
if ( evn . evn . shiftKey )
angle =
config . rotationSnappingAngles . find (
( v ) => Math . abs ( v - angle ) < config . rotationSnappingDistance
2023-01-13 02:00:34 +00:00
) ? ? angle ;
2023-01-12 04:00:06 +00:00
2023-01-13 00:49:06 +00:00
state . selected . rotation = angle ;
2023-01-12 04:00:06 +00:00
}
2023-01-13 00:49:06 +00:00
} ;
// Handles left mouse drag events
state . dragcb = ( evn ) => {
const { x , y , sx , sy } = _tool . _process _cursor ( evn , state . snapToGrid ) ;
if ( state . selected ) transform ( evn , x , y , sx , sy ) ;
2023-01-12 04:00:06 +00:00
if ( selection . exists ) selection . dragcb ( evn ) ;
2022-11-24 15:30:13 +00:00
} ;
2022-11-25 18:22:16 +00:00
// Handles left mouse drag end events
2023-01-17 02:20:13 +00:00
/** @type {(bb: BoundingBox) => void} */
const select = ( bb ) => {
const canvas = document . createElement ( "canvas" ) ;
canvas . width = bb . w ;
canvas . height = bb . h ;
canvas
. getContext ( "2d" )
. drawImage ( uil . canvas , bb . x , bb . y , bb . w , bb . h , 0 , 0 , bb . w , bb . h ) ;
uil . ctx . clearRect ( bb . x , bb . y , bb . w , bb . h ) ;
state . original = {
... bb ,
sx : bb . center . x ,
sy : bb . center . y ,
layer : uil . layer ,
} ;
state . selected = new _tool . MarqueeSelection ( canvas , bb . center ) ;
2023-04-09 02:05:22 +00:00
state . ctxmenu . mirrorSelectionCheckbox . disabled = false ;
2023-01-17 02:20:13 +00:00
state . redraw ( ) ;
} ;
2023-01-11 03:36:14 +00:00
state . dragendcb = ( evn ) => {
2023-01-12 04:00:06 +00:00
const { x , y , sx , sy } = _tool . _process _cursor ( evn , state . snapToGrid ) ;
2023-01-11 03:36:14 +00:00
if ( selection . exists ) {
2023-01-12 04:00:06 +00:00
selection . dragendcb ( evn ) ;
const bb = selection . bb ;
2023-04-05 22:02:28 +00:00
state . backupBB = bb ;
2023-01-12 04:00:06 +00:00
state . reset ( ) ;
2023-01-17 02:20:13 +00:00
if ( selection . exists && bb . w !== 0 && bb . h !== 0 ) select ( bb ) ;
2023-01-11 03:36:14 +00:00
selection . deselect ( ) ;
}
2023-01-13 00:49:06 +00:00
if ( state . selected ) transform ( evn , x , y , sx , sy ) ;
2023-01-12 04:00:06 +00:00
moving = null ;
scaling = null ;
2023-01-13 00:49:06 +00:00
rotating = false ;
2023-01-12 04:00:06 +00:00
2023-01-11 03:36:14 +00:00
state . redraw ( ) ;
} ;
2022-11-24 15:30:13 +00:00
2022-11-25 18:22:16 +00:00
// Handler for right clicks. Basically resets everything
2022-11-24 15:30:13 +00:00
state . cancelcb = ( evn ) => {
2022-11-29 20:55:25 +00:00
state . reset ( ) ;
2022-11-24 15:30:13 +00:00
} ;
2024-08-25 22:24:28 +00:00
state . keydowncb = ( evn ) => { } ;
2024-08-26 14:44:37 +00:00
// Keyboard callbacks
2024-08-26 02:28:36 +00:00
state . keyclickcb = ( evn ) => { } ;
2024-08-25 22:24:28 +00:00
// Register Delete Shortcut
2024-08-26 02:28:36 +00:00
state . delcb = ( evn ) => { state . applyTransform ( true , false , false , false ) ; } ;
2024-08-25 22:24:28 +00:00
// Register Escape Shortcut
2024-08-26 02:28:36 +00:00
state . escapecb = ( evn ) => { state . reset ( false ) ; } ;
2024-08-25 22:24:28 +00:00
2024-08-26 14:44:37 +00:00
// Register Shift-Delete Shortcut - Delete Outside Selection and Apply
2024-08-26 02:28:36 +00:00
state . sdelcb = ( evn ) => { state . applyTransform ( false , true , false , false ) ; } ;
2024-08-26 14:44:37 +00:00
// Register Enter Shortcut - Apply Transform (Delegates to clickcb)
2024-08-26 02:28:36 +00:00
state . entercb = ( evn ) => { state . clickcb ( evn ) ; } ;
2024-08-26 14:44:37 +00:00
// Register Ctrl-Enter Shortcut - Copy Selection to new layer, restore original
2024-08-26 02:28:36 +00:00
state . ctentercb = ( evn ) => { state . applyTransform ( false , false , true , true ) ; } ;
2024-08-26 14:44:37 +00:00
// Register Shift-Enter Shortcut - Move Selection to new layer
2024-08-26 02:28:36 +00:00
state . sentercb = ( evn ) => { state . applyTransform ( false , false , true , false ) ; } ;
2024-08-26 14:44:37 +00:00
// Register Ctrl-Shift-Enter Shortcut - Copy Visible Selection to new layer
2024-08-26 02:28:36 +00:00
state . sctentercb = async ( evn ) => {
var selectBB =
state . selected . bb != undefined
? state . selected . bb
: state . backupBB ;
const canvas = uil . getVisible ( selectBB , {
categories : [ "image" , "user" , "select-display" ] ,
} ) ;
await commands . runCommand ( "addLayer" , "Added Layer" ) ;
await commands . runCommand ( "drawImage" , "Transform Tool Apply" ,
{
image : canvas ,
... selectBB ,
}
) ;
state . reset ( false ) ;
} ;
2022-11-25 03:34:34 +00:00
2023-01-17 02:20:13 +00:00
// Register Ctrl-A Shortcut
state . ctrlacb = ( ) => {
2024-08-26 13:25:22 +00:00
state . reset ( false ) ; // Reset to preserve selected content
2023-01-17 02:20:13 +00:00
try {
const { bb } = cropCanvas ( uil . canvas ) ;
select ( bb ) ;
} catch ( e ) {
// Ignore errors
}
} ;
state . ctrlsacb = ( ) => {
2024-08-25 21:09:35 +00:00
state . reset ( false ) ; // Reset to preserve selected content
2023-01-17 02:20:13 +00:00
// Shift Key selects based on all visible layer information
const tl = { x : Infinity , y : Infinity } ;
const br = { x : - Infinity , y : - Infinity } ;
uil . layers . forEach ( ( { layer } ) => {
try {
const { bb } = cropCanvas ( layer . canvas ) ;
tl . x = Math . min ( bb . tl . x , tl . x ) ;
tl . y = Math . min ( bb . tl . y , tl . y ) ;
br . x = Math . max ( bb . br . x , br . x ) ;
br . y = Math . max ( bb . br . y , br . y ) ;
} catch ( e ) {
// Ignore errors
}
} ) ;
if ( Number . isFinite ( br . x - tl . y ) ) {
select ( BoundingBox . fromStartEnd ( tl , br ) ) ;
}
} ;
2022-11-25 03:34:34 +00:00
// Register Ctrl-C/V Shortcut
2022-11-25 18:22:16 +00:00
// Handles copying
state . ctrlccb = ( evn , cut = false ) => {
2023-01-16 23:54:46 +00:00
if ( ! state . selected ) return ;
if (
isCanvasBlank (
0 ,
0 ,
state . selected . canvas . width ,
state . selected . canvas . height ,
state . selected . canvas
)
)
return ;
2022-11-29 20:55:25 +00:00
// We create a new canvas to store the data
state . clipboard . copy = document . createElement ( "canvas" ) ;
2023-01-16 23:54:46 +00:00
state . clipboard . copy . width = state . selected . canvas . width ;
state . clipboard . copy . height = state . selected . canvas . height ;
2022-11-29 20:55:25 +00:00
const ctx = state . clipboard . copy . getContext ( "2d" ) ;
ctx . clearRect ( 0 , 0 , state . selected . w , state . selected . h ) ;
2023-01-16 23:54:46 +00:00
ctx . drawImage ( state . selected . canvas , 0 , 0 ) ;
2022-11-29 20:55:25 +00:00
// If cutting, we reverse the selection and erase the selection area
if ( cut ) {
const aux = state . original ;
state . reset ( ) ;
2022-11-25 18:22:16 +00:00
2023-01-25 02:37:38 +00:00
commands . runCommand ( "eraseImage" , "Cut Image" , aux , {
extra : {
log : ` Cut to clipboard a selected area at x: ${ aux . x } , y: ${ aux . y } , width: ${ aux . w } , height: ${ aux . h } from layer ${ state . original . layer . id } ` ,
} ,
} ) ;
2022-11-29 20:55:25 +00:00
}
2022-11-25 03:34:34 +00:00
2022-11-29 20:55:25 +00:00
// Because firefox needs manual activation of the feature
if ( state . useClipboard ) {
// Send to clipboard
state . clipboard . copy . toBlob ( ( blob ) => {
const item = new ClipboardItem ( { "image/png" : blob } ) ;
2022-12-03 14:40:40 +00:00
navigator . clipboard &&
navigator . clipboard . write ( [ item ] ) . catch ( ( e ) => {
console . warn ( "Error sending to clipboard" ) ;
console . warn ( e ) ;
} ) ;
2022-11-29 20:55:25 +00:00
} ) ;
2022-11-25 03:34:34 +00:00
}
} ;
2022-11-25 18:22:16 +00:00
// Handles pasting
2023-01-16 23:54:46 +00:00
state . ctrlvcb = async ( evn ) => {
2022-11-25 03:34:34 +00:00
if ( state . useClipboard ) {
2022-11-25 18:22:16 +00:00
// If we use the clipboard, do some proccessing of clipboard data (ugly but kind of minimum required)
2022-12-03 14:40:40 +00:00
navigator . clipboard &&
navigator . clipboard . read ( ) . then ( ( items ) => {
for ( const item of items ) {
for ( const type of item . types ) {
if ( type . startsWith ( "image/" ) ) {
2022-12-26 13:35:33 +00:00
item . getType ( type ) . then ( async ( blob ) => {
2022-12-03 14:40:40 +00:00
// Converts blob to image
const url = window . URL || window . webkitURL ;
const image = document . createElement ( "img" ) ;
2022-12-26 13:35:33 +00:00
image . src = url . createObjectURL ( blob ) ;
await image . decode ( ) ;
2022-12-03 14:40:40 +00:00
tools . stamp . enable ( {
image ,
back : tools . selecttransform . enable ,
} ) ;
2022-11-25 03:34:34 +00:00
} ) ;
2022-12-03 14:40:40 +00:00
}
2022-11-25 03:34:34 +00:00
}
}
2022-12-03 14:40:40 +00:00
} ) ;
2022-11-25 03:34:34 +00:00
} else if ( state . clipboard . copy ) {
2022-11-25 18:22:16 +00:00
// Use internal clipboard
2022-11-25 03:34:34 +00:00
const image = document . createElement ( "img" ) ;
image . src = state . clipboard . copy . toDataURL ( ) ;
2023-01-16 23:54:46 +00:00
await image . decode ( ) ;
2022-11-25 03:34:34 +00:00
2022-11-25 18:22:16 +00:00
// Send to stamp, as clipboard temporary data
2022-11-25 03:34:34 +00:00
tools . stamp . enable ( {
image ,
back : tools . selecttransform . enable ,
} ) ;
}
} ;
2022-11-25 18:22:16 +00:00
// Cut shortcut. Basically, send to copy handler
state . ctrlxcb = ( evn ) => {
state . ctrlccb ( evn , true ) ;
2022-11-24 15:30:13 +00:00
} ;
2024-08-25 22:24:28 +00:00
// Apply Transform and Reset State, optionally erase Selection or Clear Original Layer
2024-08-26 14:44:37 +00:00
// newLayer defaults to null, overriding the forced variants if explicitly set to false
2024-08-25 22:24:28 +00:00
// Only checks if Selection exists and content has been selected
// Does not check if content has been transformed, eg for deletion/applying to new layer
2024-08-26 14:44:37 +00:00
state . applyTransform = async ( eraseSelected = false , clearLayer = false , newLayer = null , keepOriginal = false ) => {
2024-08-26 19:55:35 +00:00
const isBlank = ! state . selected ||
2024-08-26 02:28:36 +00:00
isCanvasBlank ( 0 , 0 , state . selected . canvas . width , state . selected . canvas . height , state . selected . canvas ) ;
2024-08-25 22:24:28 +00:00
// Just reset state if nothing is selected, unless Clearing layer
2024-08-26 02:28:36 +00:00
if ( ! state . selected || ! clearLayer && isBlank ) {
2024-08-25 22:24:28 +00:00
state . reset ( false ) ;
return ;
}
// Put original image back
state . original . layer . ctx . drawImage (
state . selected . canvas ,
state . original . x ,
state . original . y
) ;
// Erase Entire Layer
2024-08-26 02:28:36 +00:00
if ( clearLayer ) await commands . runCommand (
2024-08-25 22:24:28 +00:00
"eraseImage" ,
"Transform Tool Erase" ,
{
... state . original . layer . bb ,
layer : state . original . layer ,
} ,
{
extra : {
log : ` Erased layer ${ state . original . layer . id } ` ,
} ,
}
2024-08-26 02:28:36 +00:00
) ;
2024-08-25 22:24:28 +00:00
// Erase Original Selection Area
2024-08-26 14:44:37 +00:00
else if ( eraseSelected || ! keepOriginal ) await commands . runCommand (
2024-08-25 22:24:28 +00:00
"eraseImage" ,
"Transform Tool Erase" ,
{
layer : state . original . layer ,
x : state . original . x ,
y : state . original . y ,
w : state . selected . canvas . width ,
h : state . selected . canvas . height ,
} ,
{
extra : {
log : ` Erased original selection area at x: ${ state . original . x } , y: ${ state . original . y } , width: ${ state . selected . canvas . width } , height: ${ state . selected . canvas . height } from layer ${ state . original . layer . id } ` ,
} ,
}
) ;
2024-08-26 02:28:36 +00:00
// Selection erased or was blank, no need to draw anything
if ( eraseSelected || isBlank ) {
state . reset ( true ) ;
return ;
2024-08-25 22:24:28 +00:00
}
// Draw Image
const { canvas , bb } = cropCanvas ( state . originalDisplayLayer . canvas , {
border : 10 ,
} ) ;
2024-08-26 02:28:36 +00:00
if ( ( newLayer ? ? state . toNewLayer ) && ! clearLayer )
await commands . runCommand ( "addLayer" , "Added Layer" ) ;
2024-08-25 22:24:28 +00:00
let commandLog = "" ;
const addline = ( v , newline = true ) => {
commandLog += v ;
if ( newline ) commandLog += "\n" ;
} ;
addline (
` Draw selected area to x: ${ bb . x } , y: ${ bb . y } , width: ${ bb . w } , height: ${ bb . h } to layer ${ state . original . layer . id } `
) ;
addline (
` - translation: (x: ${ state . selected . position . x } , y: ${ state . selected . position . y } ) `
) ;
addline (
` - rotation : ${
Math . round ( 1000 * ( ( 180 * state . selected . rotation ) / Math . PI ) ) /
1000
} degrees ` ,
false
) ;
2024-08-26 02:28:36 +00:00
await commands . runCommand (
2024-08-25 22:24:28 +00:00
"drawImage" ,
"Transform Tool Apply" ,
{
image : canvas ,
... bb ,
} ,
{
extra : {
log : commandLog ,
} ,
}
) ;
state . reset ( true ) ;
}
2022-11-24 15:30:13 +00:00
} ,
populateContextMenu : ( menu , state ) => {
if ( ! state . ctxmenu ) {
state . ctxmenu = { } ;
2022-11-25 18:22:16 +00:00
2022-11-24 15:30:13 +00:00
// Snap To Grid Checkbox
state . ctxmenu . snapToGridLabel = _toolbar _input . checkbox (
state ,
2023-02-19 15:41:46 +00:00
"openoutpaint/select-snaptogrid" ,
2022-11-24 15:30:13 +00:00
"snapToGrid" ,
2023-01-03 00:12:18 +00:00
"Snap To Grid" ,
"icon-grid"
) . checkbox ;
2022-11-25 18:22:16 +00:00
2022-11-25 03:34:34 +00:00
// Keep Aspect Ratio
state . ctxmenu . keepAspectRatioLabel = _toolbar _input . checkbox (
state ,
2023-02-19 15:41:46 +00:00
"openoutpaint/select-keepaspectratio" ,
2022-11-25 03:34:34 +00:00
"keepAspectRatio" ,
2023-01-03 00:12:18 +00:00
"Keep Aspect Ratio" ,
"icon-maximize"
) . checkbox ;
2022-11-25 18:22:16 +00:00
2023-04-09 02:05:22 +00:00
// Mirroring
state . onMirror = ( ) => {
if ( state . selected ) {
const scale = state . selected . scale ;
scale . x *= - 1 ;
state . selected . scale = scale ;
state . redraw ( ) ;
}
} ;
const { checkbox : mirrorCheckbox , setValue : _mirrorSetValue } =
_toolbar _input . checkbox (
state ,
"openoutpaint/select-mirror" ,
"mirrorSelection" ,
"Mirror Selection" ,
"icon-flip-horizontal" ,
( v ) => {
state . onMirror ( ) ;
}
) ;
state . ctxmenu . mirrorSelectionCheckbox = mirrorCheckbox ;
state . ctxmenu . mirrorSelectionCheckbox . disabled = true ;
_mirrorSetValue ( false ) ;
state . mirrorSetValue = ( v ) => {
_mirrorSetValue ( v ) ;
if ( v !== state . mirrorSelection ) {
state . onMirror ( ) ;
}
} ;
2022-11-25 03:34:34 +00:00
// Use Clipboard
const clipboardCheckbox = _toolbar _input . checkbox (
state ,
2023-02-19 15:41:46 +00:00
"openoutpaint/select-useclipboard" ,
2022-11-25 03:34:34 +00:00
"useClipboard" ,
2023-01-03 00:12:18 +00:00
"Use clipboard" ,
"icon-clipboard-list"
2022-11-25 03:34:34 +00:00
) ;
2023-01-03 00:12:18 +00:00
state . ctxmenu . useClipboardLabel = clipboardCheckbox . checkbox ;
2022-12-03 14:40:40 +00:00
if ( ! ( navigator . clipboard && navigator . clipboard . write ) )
2022-11-25 03:34:34 +00:00
clipboardCheckbox . checkbox . disabled = true ; // Disable if not available
2024-08-25 22:24:28 +00:00
// toNewLayer
state . ctxmenu . toNewLayerLabel = _toolbar _input . checkbox (
state ,
"openoutpaint/select-toNewLayer" ,
"toNewLayer" ,
"Always Create New Layer" ,
"icon-file-plus"
) . checkbox ;
2022-11-25 16:16:22 +00:00
2022-12-21 15:07:29 +00:00
// Selection Peek Opacity
state . ctxmenu . selectionPeekOpacitySlider = _toolbar _input . slider (
state ,
2023-02-19 15:41:46 +00:00
"openoutpaint/select-peekopacity" ,
2022-12-21 15:07:29 +00:00
"selectionPeekOpacity" ,
"Peek Opacity" ,
{
min : 0 ,
max : 100 ,
step : 10 ,
textStep : 1 ,
cb : ( ) => {
state . redraw ( ) ;
} ,
}
) . slider ;
2022-11-25 16:16:22 +00:00
// Some useful actions to do with selection
const actionArray = document . createElement ( "div" ) ;
actionArray . classList . add ( "button-array" ) ;
2022-11-25 18:22:16 +00:00
// Save button
2022-11-25 16:16:22 +00:00
const saveSelectionButton = document . createElement ( "button" ) ;
saveSelectionButton . classList . add ( "button" , "tool" ) ;
2024-08-26 02:28:36 +00:00
saveSelectionButton . innerHTML = "Save Sel." ; // nbsp as a quick hack for unwanted text wrapping
2022-11-25 16:16:22 +00:00
saveSelectionButton . title = "Saves Selection" ;
saveSelectionButton . onclick = ( ) => {
downloadCanvas ( {
cropToContent : false ,
2023-01-12 04:04:59 +00:00
canvas : state . selected . canvas ,
2022-11-25 16:16:22 +00:00
} ) ;
} ;
2022-11-25 18:22:16 +00:00
// Save as Resource Button
2022-11-25 16:16:22 +00:00
const createResourceButton = document . createElement ( "button" ) ;
createResourceButton . classList . add ( "button" , "tool" ) ;
createResourceButton . textContent = "Resource" ;
createResourceButton . title = "Saves Selection as a Resource" ;
createResourceButton . onclick = ( ) => {
const image = document . createElement ( "img" ) ;
2023-01-12 04:04:59 +00:00
image . src = state . selected . canvas . toDataURL ( ) ;
2022-12-04 02:07:53 +00:00
image . onload = ( ) => {
tools . stamp . state . addResource ( "Selection Resource" , image ) ;
tools . stamp . enable ( ) ;
} ;
2022-11-25 16:16:22 +00:00
} ;
2024-08-26 02:28:36 +00:00
const copyNewLayerButton = document . createElement ( "button" ) ;
copyNewLayerButton . classList . add ( "button" , "tool" ) ;
copyNewLayerButton . textContent = "Layer" ;
copyNewLayerButton . title = "Copies selection to a new Layer (Ctrl+Enter)" ;
copyNewLayerButton . onclick = ( ) => { state . applyTransform ( false , false , true , true ) ; } ;
// Dummy button for saving active selection
const ActiveSelectionButton = document . createElement ( "button" ) ;
ActiveSelectionButton . classList . add ( "button" , "tool" ) ;
ActiveSelectionButton . textContent = "📄" ;
2024-08-26 14:44:37 +00:00
ActiveSelectionButton . title = "Commands Applied to the Current Selection" ;
2024-08-26 02:28:36 +00:00
ActiveSelectionButton . disabled = true ;
2022-11-25 16:16:22 +00:00
actionArray . appendChild ( saveSelectionButton ) ;
actionArray . appendChild ( createResourceButton ) ;
2024-08-26 02:28:36 +00:00
actionArray . appendChild ( copyNewLayerButton ) ;
actionArray . appendChild ( ActiveSelectionButton ) ;
2022-11-25 16:16:22 +00:00
2022-12-21 15:07:29 +00:00
// Some useful actions to do with selection
const visibleActionArray = document . createElement ( "div" ) ;
visibleActionArray . classList . add ( "button-array" ) ;
// Save Visible button
const saveVisibleSelectionButton = document . createElement ( "button" ) ;
saveVisibleSelectionButton . classList . add ( "button" , "tool" ) ;
2024-08-26 02:28:36 +00:00
saveVisibleSelectionButton . innerHTML = "Save Vis." ; // nbsp as a quick hack for unwanted text wrapping
saveVisibleSelectionButton . title = "Saves Visible Selection And Download" ;
2022-12-21 15:07:29 +00:00
saveVisibleSelectionButton . onclick = ( ) => {
2023-02-15 02:44:37 +00:00
console . debug ( state . selected ) ;
console . debug ( state . selected . bb ) ;
2023-04-05 22:02:28 +00:00
var selectBB =
state . selected . bb != undefined
? state . selected . bb
: state . backupBB ;
const canvas = uil . getVisible ( selectBB , {
2023-02-15 02:44:37 +00:00
categories : [ "image" , "user" , "select-display" ] ,
} ) ;
2022-12-21 15:07:29 +00:00
downloadCanvas ( {
cropToContent : false ,
canvas ,
} ) ;
} ;
// Save Visible as Resource Button
const createVisibleResourceButton = document . createElement ( "button" ) ;
createVisibleResourceButton . classList . add ( "button" , "tool" ) ;
2024-08-26 02:28:36 +00:00
createVisibleResourceButton . textContent = "Resource" ;
2022-12-21 15:07:29 +00:00
createVisibleResourceButton . title =
"Saves Visible Selection as a Resource" ;
createVisibleResourceButton . onclick = ( ) => {
2023-04-05 22:02:28 +00:00
var selectBB =
state . selected . bb != undefined
? state . selected . bb
: state . backupBB ;
const canvas = uil . getVisible ( selectBB , {
2023-02-15 02:44:37 +00:00
categories : [ "image" , "user" , "select-display" ] ,
} ) ;
2022-12-21 15:07:29 +00:00
const image = document . createElement ( "img" ) ;
image . src = canvas . toDataURL ( ) ;
image . onload = ( ) => {
tools . stamp . state . addResource ( "Selection Resource" , image ) ;
tools . stamp . enable ( ) ;
} ;
} ;
2024-08-26 02:28:36 +00:00
// Copy To Layer Buttons
const copyVisNewLayerButton = document . createElement ( "button" ) ;
copyVisNewLayerButton . classList . add ( "button" , "tool" ) ;
copyVisNewLayerButton . textContent = "Layer" ;
copyVisNewLayerButton . title = "Copies Visible Selection to a new Layer (Ctrl+Shift+Enter)" ;
copyVisNewLayerButton . onclick = ( e ) => { state . sctentercb ( e ) ; } ;
// Dummy button for saving visible Selection
const VisibleSelectionButton = document . createElement ( "button" ) ;
VisibleSelectionButton . classList . add ( "button" , "tool" ) ;
VisibleSelectionButton . textContent = "👁" ;
2024-08-26 14:44:37 +00:00
VisibleSelectionButton . title = "Commands Applied to All Visible Content In the Selected Area" ;
2024-08-26 02:28:36 +00:00
VisibleSelectionButton . disabled = true ;
2022-12-21 15:07:29 +00:00
visibleActionArray . appendChild ( saveVisibleSelectionButton ) ;
visibleActionArray . appendChild ( createVisibleResourceButton ) ;
2024-08-26 02:28:36 +00:00
visibleActionArray . appendChild ( copyVisNewLayerButton ) ;
visibleActionArray . appendChild ( VisibleSelectionButton ) ;
2022-12-21 15:07:29 +00:00
2024-08-25 22:24:28 +00:00
const actionArrayRow3 = document . createElement ( "div" ) ;
actionArrayRow3 . classList . add ( "button-array" ) ;
// Clear Button
const applyClearButton = document . createElement ( "button" ) ;
applyClearButton . classList . add ( "button" , "tool" ) ;
2024-08-26 02:28:36 +00:00
applyClearButton . textContent = "Isolate" ;
2024-08-25 22:38:07 +00:00
applyClearButton . title = "Erases everything in the current layer outside the selection (Shift+Delete)" ;
2024-08-25 22:24:28 +00:00
applyClearButton . onclick = ( ) => { state . applyTransform ( false , true , false , false ) ; } ;
// Erase Button
const eraseSelectionButton = document . createElement ( "button" ) ;
eraseSelectionButton . classList . add ( "button" , "tool" ) ;
eraseSelectionButton . textContent = "Erase" ;
eraseSelectionButton . title = "Erases current selection (Delete)" ;
eraseSelectionButton . onclick = ( ) => { state . applyTransform ( true , false , false , false ) ; } ;
2024-08-26 02:28:36 +00:00
// Apply To New Layer button
const applyNewLayerButton = document . createElement ( "button" ) ;
applyNewLayerButton . classList . add ( "button" , "tool" ) ;
applyNewLayerButton . textContent = "Extract" ;
applyNewLayerButton . title = "Moves Selection to a New Layer (Shift+Enter)" ;
applyNewLayerButton . onclick = ( ) => { state . applyTransform ( false , false , true , false ) ; } ;
actionArrayRow3 . appendChild ( applyClearButton ) ;
actionArrayRow3 . appendChild ( eraseSelectionButton ) ;
actionArrayRow3 . appendChild ( applyNewLayerButton ) ;
2024-08-25 22:24:28 +00:00
2022-11-25 18:22:16 +00:00
// Disable buttons (if nothing is selected)
2022-11-25 16:16:22 +00:00
state . ctxmenu . disableButtons = ( ) => {
saveSelectionButton . disabled = true ;
createResourceButton . disabled = true ;
2022-12-21 15:07:29 +00:00
saveVisibleSelectionButton . disabled = true ;
createVisibleResourceButton . disabled = true ;
2024-08-25 22:24:28 +00:00
applyNewLayerButton . disabled = true ;
copyNewLayerButton . disabled = true ;
applyClearButton . disabled = true ;
eraseSelectionButton . disabled = true ;
2024-08-26 02:28:36 +00:00
copyVisNewLayerButton . disabled = true ;
2022-11-25 16:16:22 +00:00
} ;
2022-11-25 18:22:16 +00:00
2023-01-17 03:03:43 +00:00
// Enable buttons (if something is selected)
2022-11-25 16:16:22 +00:00
state . ctxmenu . enableButtons = ( ) => {
saveSelectionButton . disabled = "" ;
createResourceButton . disabled = "" ;
2022-12-21 15:07:29 +00:00
saveVisibleSelectionButton . disabled = "" ;
createVisibleResourceButton . disabled = "" ;
2024-08-25 22:24:28 +00:00
applyNewLayerButton . disabled = "" ;
copyNewLayerButton . disabled = "" ;
applyClearButton . disabled = "" ;
eraseSelectionButton . disabled = "" ;
2024-08-26 02:28:36 +00:00
copyVisNewLayerButton . disabled = "" ;
2022-11-25 16:16:22 +00:00
} ;
2024-08-25 22:24:28 +00:00
2022-11-25 16:16:22 +00:00
state . ctxmenu . actionArray = actionArray ;
2022-12-21 15:07:29 +00:00
state . ctxmenu . visibleActionArray = visibleActionArray ;
2024-08-25 22:24:28 +00:00
state . ctxmenu . actionArrayRow3 = actionArrayRow3 ;
2023-01-17 03:03:43 +00:00
// Send Selection to Destination
state . ctxmenu . sendSelected = document . createElement ( "select" ) ;
state . ctxmenu . sendSelected . style . width = "100%" ;
state . ctxmenu . sendSelected . addEventListener ( "change" , ( evn ) => {
const v = evn . target . value ;
if ( state . selected && v !== "None" )
global . webui && global . webui . sendTo ( state . selected . canvas , v ) ;
evn . target . value = "None" ;
} ) ;
let opt = document . createElement ( "option" ) ;
opt . textContent = "Send To..." ;
opt . value = "None" ;
state . ctxmenu . sendSelected . appendChild ( opt ) ;
2022-11-24 15:30:13 +00:00
}
2023-01-03 00:12:18 +00:00
const array = document . createElement ( "div" ) ;
array . classList . add ( "checkbox-array" ) ;
array . appendChild ( state . ctxmenu . snapToGridLabel ) ;
array . appendChild ( state . ctxmenu . keepAspectRatioLabel ) ;
2023-04-09 02:05:22 +00:00
array . appendChild ( state . ctxmenu . mirrorSelectionCheckbox ) ;
2023-01-03 00:12:18 +00:00
array . appendChild ( state . ctxmenu . useClipboardLabel ) ;
2024-08-25 22:24:28 +00:00
array . appendChild ( state . ctxmenu . toNewLayerLabel ) ;
2023-01-03 00:12:18 +00:00
menu . appendChild ( array ) ;
2022-12-21 15:07:29 +00:00
menu . appendChild ( state . ctxmenu . selectionPeekOpacitySlider ) ;
2022-11-25 16:16:22 +00:00
menu . appendChild ( state . ctxmenu . actionArray ) ;
2022-12-21 15:07:29 +00:00
menu . appendChild ( state . ctxmenu . visibleActionArray ) ;
2024-08-25 22:24:28 +00:00
menu . appendChild ( state . ctxmenu . actionArrayRow3 ) ;
2023-01-17 03:03:43 +00:00
if ( global . webui && global . webui . destinations ) {
while ( state . ctxmenu . sendSelected . lastChild . value !== "None" ) {
state . ctxmenu . sendSelected . removeChild (
state . ctxmenu . sendSelected . lastChild
) ;
}
global . webui . destinations . forEach ( ( dst ) => {
const opt = document . createElement ( "option" ) ;
opt . textContent = dst . name ;
opt . value = dst . id ;
state . ctxmenu . sendSelected . appendChild ( opt ) ;
} ) ;
menu . appendChild ( state . ctxmenu . sendSelected ) ;
}
2022-11-24 15:30:13 +00:00
} ,
2022-11-25 03:34:34 +00:00
shortcut : "S" ,
2022-11-24 15:30:13 +00:00
}
) ;