2022-11-24 09:30:13 -06:00
const selectTransformTool = ( ) =>
toolbar . registerTool (
2022-12-18 17:50:40 -06:00
"./res/icons/box-select.svg" ,
2022-11-24 09:30:13 -06:00
"Select Image" ,
( state , opt ) => {
// Draw new cursor immediately
2022-12-16 21:55:53 -06:00
ovLayer . clear ( ) ;
2022-11-29 14:55:25 -06:00
state . movecb ( mouse . coords . world . pos ) ;
2022-11-24 09:30:13 -06:00
2022-11-25 12:22:16 -06:00
// Canvas left mouse handlers
2022-11-29 14:55:25 -06: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-10 21:36:14 -06:00
mouse . listen . world . btn . left . ondrag . on ( state . dragcb ) ;
2022-11-29 14:55:25 -06:00
mouse . listen . world . btn . left . ondragend . on ( state . dragendcb ) ;
2022-11-24 09:30:13 -06:00
2022-11-25 12:22:16 -06:00
// Canvas right mouse handler
2022-11-29 14:55:25 -06:00
mouse . listen . world . btn . right . onclick . on ( state . cancelcb ) ;
2022-11-24 21:34:34 -06:00
2022-11-25 12:22:16 -06:00
// Keyboard click handlers
2022-11-24 21:34:34 -06:00
keyboard . listen . onkeyclick . on ( state . keyclickcb ) ;
keyboard . listen . onkeydown . on ( state . keydowncb ) ;
2022-11-25 12:22:16 -06:00
2022-12-21 09:07:29 -06:00
// Layer system handlers
uil . onactive . on ( state . uilayeractivecb ) ;
2022-11-25 12:22:16 -06:00
// Registers keyboard shortcuts
2023-01-16 20:20:13 -06:00
keyboard . onShortcut ( { ctrl : true , key : "KeyA" } , state . ctrlacb ) ;
keyboard . onShortcut (
{ ctrl : true , shift : true , key : "KeyA" } ,
state . ctrlsacb
) ;
2022-11-24 21:34:34 -06: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-08 21:05:22 -05:00
keyboard . onShortcut ( { key : "Equal" } , state . togglemirror ) ;
2022-11-25 10:16:22 -06:00
2023-04-08 21:05:22 -05:00
state . ctxmenu . mirrorSelectionCheckbox . disabled = true ;
2022-11-25 10:16:22 -06:00
state . selected = null ;
2023-01-11 22:00:06 -06:00
// Register Layer
state . originalDisplayLayer = imageCollection . registerLayer ( null , {
after : uil . layer ,
category : "select-display" ,
} ) ;
2022-11-24 09:30:13 -06:00
} ,
( state , opt ) => {
2022-11-25 12:22:16 -06:00
// Clear all those listeners and shortcuts we set up
2022-11-29 14:55:25 -06: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-10 21:36:14 -06:00
mouse . listen . world . btn . left . ondrag . clear ( state . dragcb ) ;
2022-11-29 14:55:25 -06:00
mouse . listen . world . btn . left . ondragend . clear ( state . dragendcb ) ;
2022-11-24 09:30:13 -06:00
2022-11-29 14:55:25 -06:00
mouse . listen . world . btn . right . onclick . clear ( state . cancelcb ) ;
2022-11-24 21:34:34 -06:00
keyboard . listen . onkeyclick . clear ( state . keyclickcb ) ;
keyboard . listen . onkeydown . clear ( state . keydowncb ) ;
2023-01-16 20:20:13 -06:00
keyboard . deleteShortcut ( state . ctrlacb , "KeyA" ) ;
keyboard . deleteShortcut ( state . ctrlsacb , "KeyA" ) ;
2022-11-24 21:34:34 -06:00
keyboard . deleteShortcut ( state . ctrlccb , "KeyC" ) ;
keyboard . deleteShortcut ( state . ctrlvcb , "KeyV" ) ;
keyboard . deleteShortcut ( state . ctrlxcb , "KeyX" ) ;
2023-04-08 21:05:22 -05:00
keyboard . deleteShortcut ( state . togglemirror , "Equal" ) ;
2022-11-24 21:34:34 -06:00
2022-12-21 09:07:29 -06:00
uil . onactive . clear ( state . uilayeractivecb ) ;
2022-11-25 12:22:16 -06:00
// Clear any selections
2022-11-24 21:34:34 -06:00
state . reset ( ) ;
2022-11-25 12:22:16 -06:00
// Resets cursor
2022-12-16 21:55:53 -06:00
ovLayer . clear ( ) ;
2022-12-06 09:25:06 -06:00
// Clears overlay
2022-11-29 14:55:25 -06:00
imageCollection . inputElement . style . cursor = "auto" ;
2023-01-11 22:00:06 -06:00
// Delete Layer
imageCollection . deleteLayer ( state . originalDisplayLayer ) ;
state . originalDisplayLayer = null ;
2022-11-24 09:30:13 -06:00
} ,
{
init : ( state ) => {
2022-11-24 21:34:34 -06:00
state . clipboard = { } ;
2022-11-24 09:30:13 -06:00
state . snapToGrid = true ;
2022-11-24 21:34:34 -06:00
state . keepAspectRatio = true ;
2022-12-18 20:35:48 -06:00
state . block _res _change = true ;
2022-12-03 08:40:40 -06:00
state . useClipboard = ! ! (
navigator . clipboard && navigator . clipboard . write
) ; // Use it by default if supported
2022-12-21 09:07:29 -06:00
state . selectionPeekOpacity = 40 ;
2022-11-24 21:34:34 -06:00
state . original = null ;
2022-11-25 10:16:22 -06: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-24 21:34:34 -06:00
2022-11-25 12:22:16 -06:00
// Some things to easy request for a redraw
2022-11-24 21:34:34 -06:00
state . lastMouseTarget = null ;
2022-12-21 09:07:29 -06:00
state . lastMouseMove = { x : 0 , y : 0 } ;
2022-11-24 21:34:34 -06:00
2022-12-06 09:25:06 -06:00
state . redraw = ( ) => {
2022-12-16 21:55:53 -06:00
ovLayer . clear ( ) ;
2022-11-24 21:34:34 -06:00
state . movecb ( state . lastMouseMove ) ;
} ;
2022-12-21 09:07:29 -06:00
state . uilayeractivecb = ( { uilayer } ) => {
if ( state . originalDisplayLayer ) {
state . originalDisplayLayer . moveAfter ( uilayer . layer ) ;
}
} ;
2023-01-12 18:49:06 -06:00
/** @type {{selected: Point, offset: Point} | null} */
let moving = null ;
/** @type {{handle: Point} | null} */
let scaling = null ;
let rotating = false ;
2022-11-25 12:22:16 -06:00
// Clears selection and make things right
2022-12-21 09:07:29 -06:00
state . reset = ( erase = false ) => {
if ( state . selected && ! erase )
2023-01-11 22:00:06 -06:00
state . original . layer . ctx . drawImage (
state . selected . canvas ,
2022-11-25 12:22:16 -06:00
state . original . x ,
state . original . y
2022-11-24 21:59:10 -06:00
) ;
2022-12-21 09:07:29 -06:00
if ( state . originalDisplayLayer ) {
2023-01-11 22:00:06 -06:00
state . originalDisplayLayer . clear ( ) ;
2022-12-21 09:07:29 -06:00
}
2022-11-24 21:34:34 -06:00
if ( state . dragging ) state . dragging = null ;
2023-04-08 21:05:22 -05:00
else {
state . ctxmenu . mirrorSelectionCheckbox . disabled = true ;
state . selected = null ;
}
2022-11-24 21:34:34 -06:00
2023-04-08 21:05:22 -05:00
state . mirrorSetValue ( false ) ;
2023-01-07 22:54:37 -06:00
state . rotation = 0 ;
state . original = null ;
2023-01-12 18:49:06 -06:00
moving = null ;
scaling = null ;
rotating = null ;
2023-01-07 22:54:37 -06:00
2022-12-06 09:25:06 -06:00
state . redraw ( ) ;
2022-11-24 21:34:34 -06:00
} ;
2022-11-24 09:30:13 -06:00
2023-01-08 15:21:08 -06:00
// Selection Handlers
const selection = _tool . _draggable _selection ( state ) ;
2022-11-24 09:30:13 -06:00
2023-01-11 22:00:06 -06:00
// UI Erasers
2023-01-10 21:36:14 -06:00
let eraseSelectedBox = ( ) => null ;
2023-01-11 22:00:06 -06:00
let eraseSelectedImage = ( ) => null ;
2023-01-08 15:21:08 -06:00
let eraseCursor = ( ) => null ;
2023-01-10 21:36:14 -06:00
let eraseSelection = ( ) => null ;
2023-01-11 22:00:06 -06: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
) ;
}
} ;
2023-04-08 21:05:22 -05:00
// Mirroring
state . togglemirror = ( ) => {
state . mirrorSetValue ( ! state . mirrorSelection ) ;
} ;
2023-01-11 22:00:06 -06:00
// Mouse Move Handler
2022-11-24 09:30:13 -06:00
state . movecb = ( evn ) => {
2023-01-11 22:00:06 -06:00
state . lastMouseMove = evn ;
2023-01-08 15:21:08 -06:00
// Get cursor positions
const { x , y , sx , sy } = _tool . _process _cursor ( evn , state . snapToGrid ) ;
2022-12-06 09:25:06 -06:00
2023-01-10 21:36:14 -06:00
// Erase Cursor
eraseSelectedBox ( ) ;
2023-01-11 22:00:06 -06:00
eraseSelectedImage ( ) ;
2023-01-10 21:36:14 -06:00
eraseSelection ( ) ;
2023-01-08 15:21:08 -06:00
eraseCursor ( ) ;
2023-01-10 21:36:14 -06:00
imageCollection . inputElement . style . cursor = "default" ;
2023-01-07 22:54:37 -06:00
2023-01-08 15:21:08 -06:00
// Draw Box and Selected Image
if ( state . selected ) {
2023-01-11 22:00:06 -06:00
eraseSelectedBox = state . selected . drawBox (
uiCtx ,
{ x , y } ,
viewport . c2v
) ;
if (
state . selected . hoveringBox ( x , y ) ||
2023-01-12 18:49:06 -06:00
state . selected . hoveringHandle ( x , y , viewport . zoom ) . onHandle ||
state . selected . hoveringRotateHandle ( x , y , viewport . zoom )
2023-01-11 22:00:06 -06:00
) {
imageCollection . inputElement . style . cursor = "pointer" ;
}
eraseSelectedImage = state . selected . drawImage (
state . originalDisplayLayer . ctx ,
ovCtx ,
{ opacity : state . selectionPeekOpacity / 100 }
) ;
2022-11-29 14:55:25 -06:00
}
2022-11-24 09:30:13 -06:00
2023-01-08 15:21:08 -06:00
// Draw Selection
if ( selection . exists ) {
2023-01-10 21:36:14 -06:00
uiCtx . save ( ) ;
2022-12-06 09:25:06 -06:00
uiCtx . setLineDash ( [ 2 , 2 ] ) ;
2023-01-10 21:36:14 -06:00
uiCtx . lineWidth = 2 ;
2022-12-06 09:25:06 -06:00
uiCtx . strokeStyle = "#FFF" ;
2022-11-24 09:30:13 -06:00
2023-01-10 21:36:14 -06: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 09:30:13 -06:00
} ;
2022-11-25 12:22:16 -06:00
// Handles left mouse clicks
2023-01-11 22:00:06 -06:00
state . clickcb = ( evn ) => {
if (
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 &&
2023-04-08 21:05:22 -05:00
! state . mirrorSelection &&
2023-01-11 22:00:06 -06:00
state . original . layer === uil . layer
2023-01-21 23:48:56 -06:00
) &&
! isCanvasBlank (
0 ,
0 ,
state . selected . canvas . width ,
state . selected . canvas . height ,
state . selected . canvas
2023-01-11 22:00:06 -06:00
)
) {
// Put original image back
state . original . layer . ctx . drawImage (
state . selected . canvas ,
state . original . x ,
state . original . y
) ;
// Erase Original Selection Area
2023-01-24 20:37:38 -06:00
commands . runCommand (
"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 } ` ,
} ,
}
) ;
2023-01-11 22:00:06 -06:00
// Draw Image
const { canvas , bb } = cropCanvas ( state . originalDisplayLayer . canvas , {
border : 10 ,
} ) ;
2023-01-24 20:54:30 -06:00
2023-01-24 20:37:38 -06: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
2023-01-24 20:48:09 -06:00
} degrees ` ,
false
2023-01-24 20:37:38 -06:00
) ;
2023-01-11 22:00:06 -06:00
2023-01-24 20:37:38 -06:00
commands . runCommand (
"drawImage" ,
"Transform Tool Apply" ,
{
image : canvas ,
... bb ,
} ,
{
extra : {
log : commandLog ,
} ,
}
) ;
2023-01-12 17:32:53 -06:00
state . reset ( true ) ;
} else {
state . reset ( ) ;
2023-01-11 22:00:06 -06:00
}
} ;
2022-11-25 12:22:16 -06:00
2023-01-10 21:36:14 -06:00
// Handles left mouse drag start events
2022-11-24 09:30:13 -06:00
state . dragstartcb = ( evn ) => {
2023-01-11 22:00:06 -06: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-12 18:49:06 -06:00
const hoveringHandle = state . selected . hoveringHandle (
ix ,
iy ,
viewport . zoom
) ;
const hoveringRotateHandle = state . selected . hoveringRotateHandle (
ix ,
iy ,
viewport . zoom
) ;
2023-01-11 22:00:06 -06: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-12 18:49:06 -06:00
} else if ( hoveringRotateHandle ) {
rotating = true ;
return ;
2023-01-11 22:00:06 -06:00
}
}
2023-01-10 21:36:14 -06:00
selection . dragstartcb ( evn ) ;
} ;
2023-01-12 18:49:06 -06: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-11 22:00:06 -06:00
2023-01-12 18:49:06 -06: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 ;
const xy = lscursor . y / scaling . handle . y ;
2023-04-08 21:05:22 -05:00
let xscale = 1 ;
let yscale = 1 ;
if ( ! state . keepAspectRatio ) {
xscale = xs ;
yscale = ys ;
} else {
xscale = yscale = Math . max ( xs , xy ) ;
2023-01-11 22:00:06 -06:00
}
2023-04-08 21:05:22 -05:00
state . selected . scale = { x : xscale , y : yscale } ;
2023-01-12 18:49:06 -06:00
}
2023-01-11 22:00:06 -06:00
2023-01-12 18:49:06 -06: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-11 22:00:06 -06:00
2023-01-12 18:49:06 -06:00
if ( evn . evn . shiftKey )
angle =
config . rotationSnappingAngles . find (
( v ) => Math . abs ( v - angle ) < config . rotationSnappingDistance
2023-01-12 20:00:34 -06:00
) ? ? angle ;
2023-01-11 22:00:06 -06:00
2023-01-12 18:49:06 -06:00
state . selected . rotation = angle ;
2023-01-11 22:00:06 -06:00
}
2023-01-12 18:49:06 -06: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-11 22:00:06 -06:00
if ( selection . exists ) selection . dragcb ( evn ) ;
2022-11-24 09:30:13 -06:00
} ;
2022-11-25 12:22:16 -06:00
// Handles left mouse drag end events
2023-01-16 20:20:13 -06: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-08 21:05:22 -05:00
state . ctxmenu . mirrorSelectionCheckbox . disabled = false ;
2023-01-16 20:20:13 -06:00
state . redraw ( ) ;
} ;
2023-01-10 21:36:14 -06:00
state . dragendcb = ( evn ) => {
2023-01-11 22:00:06 -06:00
const { x , y , sx , sy } = _tool . _process _cursor ( evn , state . snapToGrid ) ;
2023-01-10 21:36:14 -06:00
if ( selection . exists ) {
2023-01-11 22:00:06 -06:00
selection . dragendcb ( evn ) ;
const bb = selection . bb ;
2023-04-05 17:02:28 -05:00
state . backupBB = bb ;
2023-01-11 22:00:06 -06:00
state . reset ( ) ;
2023-01-16 20:20:13 -06:00
if ( selection . exists && bb . w !== 0 && bb . h !== 0 ) select ( bb ) ;
2023-01-10 21:36:14 -06:00
selection . deselect ( ) ;
}
2023-01-12 18:49:06 -06:00
if ( state . selected ) transform ( evn , x , y , sx , sy ) ;
2023-01-11 22:00:06 -06:00
moving = null ;
scaling = null ;
2023-01-12 18:49:06 -06:00
rotating = false ;
2023-01-11 22:00:06 -06:00
2023-01-10 21:36:14 -06:00
state . redraw ( ) ;
} ;
2022-11-24 09:30:13 -06:00
2022-11-25 12:22:16 -06:00
// Handler for right clicks. Basically resets everything
2022-11-24 09:30:13 -06:00
state . cancelcb = ( evn ) => {
2022-11-29 14:55:25 -06:00
state . reset ( ) ;
2022-11-24 09:30:13 -06:00
} ;
2022-11-25 12:22:16 -06:00
// Keyboard callbacks (For now, they just handle the "delete" key)
2022-11-24 21:34:34 -06:00
state . keydowncb = ( evn ) => { } ;
2022-11-24 09:30:13 -06:00
state . keyclickcb = ( evn ) => {
2022-11-29 14:55:25 -06:00
switch ( evn . code ) {
case "Delete" :
// Deletes selected area
state . selected &&
2023-01-24 20:37:38 -06:00
commands . runCommand (
"eraseImage" ,
"Erase Area" ,
state . selected ,
{
extra : {
log : ` [Placeholder] Delete selected area. TODO it's also broken ` ,
} ,
}
) ;
2023-04-08 21:05:22 -05:00
state . ctxmenu . mirrorSelectionCheckbox . disabled = true ;
2022-11-29 14:55:25 -06:00
state . selected = null ;
2022-12-06 09:25:06 -06:00
state . redraw ( ) ;
2022-11-24 21:34:34 -06:00
}
} ;
2023-01-16 20:20:13 -06:00
// Register Ctrl-A Shortcut
state . ctrlacb = ( ) => {
try {
const { bb } = cropCanvas ( uil . canvas ) ;
select ( bb ) ;
} catch ( e ) {
// Ignore errors
}
} ;
state . ctrlsacb = ( ) => {
// 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-24 21:34:34 -06:00
// Register Ctrl-C/V Shortcut
2022-11-25 12:22:16 -06:00
// Handles copying
state . ctrlccb = ( evn , cut = false ) => {
2023-01-16 17:54:46 -06:00
if ( ! state . selected ) return ;
if (
isCanvasBlank (
0 ,
0 ,
state . selected . canvas . width ,
state . selected . canvas . height ,
state . selected . canvas
)
)
return ;
2022-11-29 14:55:25 -06:00
// We create a new canvas to store the data
state . clipboard . copy = document . createElement ( "canvas" ) ;
2023-01-16 17:54:46 -06:00
state . clipboard . copy . width = state . selected . canvas . width ;
state . clipboard . copy . height = state . selected . canvas . height ;
2022-11-29 14:55:25 -06:00
const ctx = state . clipboard . copy . getContext ( "2d" ) ;
ctx . clearRect ( 0 , 0 , state . selected . w , state . selected . h ) ;
2023-01-16 17:54:46 -06:00
ctx . drawImage ( state . selected . canvas , 0 , 0 ) ;
2022-11-29 14:55:25 -06:00
// If cutting, we reverse the selection and erase the selection area
if ( cut ) {
const aux = state . original ;
state . reset ( ) ;
2022-11-25 12:22:16 -06:00
2023-01-24 20:37:38 -06: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 14:55:25 -06:00
}
2022-11-24 21:34:34 -06:00
2022-11-29 14:55:25 -06: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 08:40:40 -06:00
navigator . clipboard &&
navigator . clipboard . write ( [ item ] ) . catch ( ( e ) => {
console . warn ( "Error sending to clipboard" ) ;
console . warn ( e ) ;
} ) ;
2022-11-29 14:55:25 -06:00
} ) ;
2022-11-24 21:34:34 -06:00
}
} ;
2022-11-25 12:22:16 -06:00
// Handles pasting
2023-01-16 17:54:46 -06:00
state . ctrlvcb = async ( evn ) => {
2022-11-24 21:34:34 -06:00
if ( state . useClipboard ) {
2022-11-25 12:22:16 -06:00
// If we use the clipboard, do some proccessing of clipboard data (ugly but kind of minimum required)
2022-12-03 08:40:40 -06: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 07:35:33 -06:00
item . getType ( type ) . then ( async ( blob ) => {
2022-12-03 08:40:40 -06:00
// Converts blob to image
const url = window . URL || window . webkitURL ;
const image = document . createElement ( "img" ) ;
2022-12-26 07:35:33 -06:00
image . src = url . createObjectURL ( blob ) ;
await image . decode ( ) ;
2022-12-03 08:40:40 -06:00
tools . stamp . enable ( {
image ,
back : tools . selecttransform . enable ,
} ) ;
2022-11-24 21:34:34 -06:00
} ) ;
2022-12-03 08:40:40 -06:00
}
2022-11-24 21:34:34 -06:00
}
}
2022-12-03 08:40:40 -06:00
} ) ;
2022-11-24 21:34:34 -06:00
} else if ( state . clipboard . copy ) {
2022-11-25 12:22:16 -06:00
// Use internal clipboard
2022-11-24 21:34:34 -06:00
const image = document . createElement ( "img" ) ;
image . src = state . clipboard . copy . toDataURL ( ) ;
2023-01-16 17:54:46 -06:00
await image . decode ( ) ;
2022-11-24 21:34:34 -06:00
2022-11-25 12:22:16 -06:00
// Send to stamp, as clipboard temporary data
2022-11-24 21:34:34 -06:00
tools . stamp . enable ( {
image ,
back : tools . selecttransform . enable ,
} ) ;
}
} ;
2022-11-25 12:22:16 -06:00
// Cut shortcut. Basically, send to copy handler
state . ctrlxcb = ( evn ) => {
state . ctrlccb ( evn , true ) ;
2022-11-24 09:30:13 -06:00
} ;
} ,
populateContextMenu : ( menu , state ) => {
if ( ! state . ctxmenu ) {
state . ctxmenu = { } ;
2022-11-25 12:22:16 -06:00
2022-11-24 09:30:13 -06:00
// Snap To Grid Checkbox
state . ctxmenu . snapToGridLabel = _toolbar _input . checkbox (
state ,
2023-02-19 09:41:46 -06:00
"openoutpaint/select-snaptogrid" ,
2022-11-24 09:30:13 -06:00
"snapToGrid" ,
2023-01-02 18:12:18 -06:00
"Snap To Grid" ,
"icon-grid"
) . checkbox ;
2022-11-25 12:22:16 -06:00
2022-11-24 21:34:34 -06:00
// Keep Aspect Ratio
state . ctxmenu . keepAspectRatioLabel = _toolbar _input . checkbox (
state ,
2023-02-19 09:41:46 -06:00
"openoutpaint/select-keepaspectratio" ,
2022-11-24 21:34:34 -06:00
"keepAspectRatio" ,
2023-01-02 18:12:18 -06:00
"Keep Aspect Ratio" ,
"icon-maximize"
) . checkbox ;
2022-11-25 12:22:16 -06:00
2023-04-08 21:05:22 -05: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-24 21:34:34 -06:00
// Use Clipboard
const clipboardCheckbox = _toolbar _input . checkbox (
state ,
2023-02-19 09:41:46 -06:00
"openoutpaint/select-useclipboard" ,
2022-11-24 21:34:34 -06:00
"useClipboard" ,
2023-01-02 18:12:18 -06:00
"Use clipboard" ,
"icon-clipboard-list"
2022-11-24 21:34:34 -06:00
) ;
2023-01-02 18:12:18 -06:00
state . ctxmenu . useClipboardLabel = clipboardCheckbox . checkbox ;
2022-12-03 08:40:40 -06:00
if ( ! ( navigator . clipboard && navigator . clipboard . write ) )
2022-11-24 21:34:34 -06:00
clipboardCheckbox . checkbox . disabled = true ; // Disable if not available
2022-11-25 10:16:22 -06:00
2022-12-21 09:07:29 -06:00
// Selection Peek Opacity
state . ctxmenu . selectionPeekOpacitySlider = _toolbar _input . slider (
state ,
2023-02-19 09:41:46 -06:00
"openoutpaint/select-peekopacity" ,
2022-12-21 09:07:29 -06:00
"selectionPeekOpacity" ,
"Peek Opacity" ,
{
min : 0 ,
max : 100 ,
step : 10 ,
textStep : 1 ,
cb : ( ) => {
state . redraw ( ) ;
} ,
}
) . slider ;
2022-11-25 10:16:22 -06:00
// Some useful actions to do with selection
const actionArray = document . createElement ( "div" ) ;
actionArray . classList . add ( "button-array" ) ;
2022-11-25 12:22:16 -06:00
// Save button
2022-11-25 10:16:22 -06:00
const saveSelectionButton = document . createElement ( "button" ) ;
saveSelectionButton . classList . add ( "button" , "tool" ) ;
2022-12-21 09:07:29 -06:00
saveSelectionButton . textContent = "Save Sel." ;
2022-11-25 10:16:22 -06:00
saveSelectionButton . title = "Saves Selection" ;
saveSelectionButton . onclick = ( ) => {
downloadCanvas ( {
cropToContent : false ,
2023-01-11 22:04:59 -06:00
canvas : state . selected . canvas ,
2022-11-25 10:16:22 -06:00
} ) ;
} ;
2022-11-25 12:22:16 -06:00
// Save as Resource Button
2022-11-25 10:16:22 -06: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-11 22:04:59 -06:00
image . src = state . selected . canvas . toDataURL ( ) ;
2022-12-03 20:07:53 -06:00
image . onload = ( ) => {
tools . stamp . state . addResource ( "Selection Resource" , image ) ;
tools . stamp . enable ( ) ;
} ;
2022-11-25 10:16:22 -06:00
} ;
actionArray . appendChild ( saveSelectionButton ) ;
actionArray . appendChild ( createResourceButton ) ;
2022-12-21 09:07:29 -06: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" ) ;
saveVisibleSelectionButton . textContent = "Save Vis." ;
saveVisibleSelectionButton . title = "Saves Visible Selection" ;
saveVisibleSelectionButton . onclick = ( ) => {
2023-02-14 20:44:37 -06:00
console . debug ( state . selected ) ;
console . debug ( state . selected . bb ) ;
2023-04-05 17:02:28 -05:00
var selectBB =
state . selected . bb != undefined
? state . selected . bb
: state . backupBB ;
const canvas = uil . getVisible ( selectBB , {
2023-02-14 20:44:37 -06:00
categories : [ "image" , "user" , "select-display" ] ,
} ) ;
2022-12-21 09:07:29 -06:00
downloadCanvas ( {
cropToContent : false ,
canvas ,
} ) ;
} ;
// Save Visible as Resource Button
const createVisibleResourceButton = document . createElement ( "button" ) ;
createVisibleResourceButton . classList . add ( "button" , "tool" ) ;
createVisibleResourceButton . textContent = "Vis. to Res." ;
createVisibleResourceButton . title =
"Saves Visible Selection as a Resource" ;
createVisibleResourceButton . onclick = ( ) => {
2023-04-05 17:02:28 -05:00
var selectBB =
state . selected . bb != undefined
? state . selected . bb
: state . backupBB ;
const canvas = uil . getVisible ( selectBB , {
2023-02-14 20:44:37 -06:00
categories : [ "image" , "user" , "select-display" ] ,
} ) ;
2022-12-21 09:07:29 -06:00
const image = document . createElement ( "img" ) ;
image . src = canvas . toDataURL ( ) ;
image . onload = ( ) => {
tools . stamp . state . addResource ( "Selection Resource" , image ) ;
tools . stamp . enable ( ) ;
} ;
} ;
visibleActionArray . appendChild ( saveVisibleSelectionButton ) ;
visibleActionArray . appendChild ( createVisibleResourceButton ) ;
2022-11-25 12:22:16 -06:00
// Disable buttons (if nothing is selected)
2022-11-25 10:16:22 -06:00
state . ctxmenu . disableButtons = ( ) => {
saveSelectionButton . disabled = true ;
createResourceButton . disabled = true ;
2022-12-21 09:07:29 -06:00
saveVisibleSelectionButton . disabled = true ;
createVisibleResourceButton . disabled = true ;
2022-11-25 10:16:22 -06:00
} ;
2022-11-25 12:22:16 -06:00
2023-01-16 21:03:43 -06:00
// Enable buttons (if something is selected)
2022-11-25 10:16:22 -06:00
state . ctxmenu . enableButtons = ( ) => {
saveSelectionButton . disabled = "" ;
createResourceButton . disabled = "" ;
2022-12-21 09:07:29 -06:00
saveVisibleSelectionButton . disabled = "" ;
createVisibleResourceButton . disabled = "" ;
2022-11-25 10:16:22 -06:00
} ;
state . ctxmenu . actionArray = actionArray ;
2022-12-21 09:07:29 -06:00
state . ctxmenu . visibleActionArray = visibleActionArray ;
2023-01-16 21:03:43 -06: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 09:30:13 -06:00
}
2023-01-02 18:12:18 -06:00
const array = document . createElement ( "div" ) ;
array . classList . add ( "checkbox-array" ) ;
array . appendChild ( state . ctxmenu . snapToGridLabel ) ;
array . appendChild ( state . ctxmenu . keepAspectRatioLabel ) ;
2023-04-08 21:05:22 -05:00
array . appendChild ( state . ctxmenu . mirrorSelectionCheckbox ) ;
2023-01-02 18:12:18 -06:00
array . appendChild ( state . ctxmenu . useClipboardLabel ) ;
menu . appendChild ( array ) ;
2022-12-21 09:07:29 -06:00
menu . appendChild ( state . ctxmenu . selectionPeekOpacitySlider ) ;
2022-11-25 10:16:22 -06:00
menu . appendChild ( state . ctxmenu . actionArray ) ;
2022-12-21 09:07:29 -06:00
menu . appendChild ( state . ctxmenu . visibleActionArray ) ;
2023-01-16 21:03:43 -06: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 09:30:13 -06:00
} ,
2022-11-24 21:34:34 -06:00
shortcut : "S" ,
2022-11-24 09:30:13 -06:00
}
) ;