diff --git a/js/editor/editordefs.js b/js/editor/editordefs.js index cd6ac1e..b588f21 100644 --- a/js/editor/editordefs.js +++ b/js/editor/editordefs.js @@ -41,7 +41,7 @@ export const TOOLS = { select_box: { icon: 'icons/tool-select-box.png', name: "Box select", - desc: "Select and manipulate rectangles.\n\n[mouse1] Select rectangle\n[shift] [mouse1] Add to selection\n\n[mouse1] Move selection\n[ctrl] [mouse1] Clone selection", + desc: "Select and manipulate rectangles.\n\n[mouse1] Select rectangle\n[shift] [mouse1] Add to selection\n[ctrl] [mouse1] Remove from selection\n\n[mouse1] Move selection\n[ctrl] [mouse1] Clone selection", affects_selection: true, op1: mouseops.SelectOperation, shortcut: 'm', diff --git a/js/editor/helpers.js b/js/editor/helpers.js index 3009e28..7cdd8b1 100644 --- a/js/editor/helpers.js +++ b/js/editor/helpers.js @@ -33,8 +33,9 @@ export class SVGConnection { export class PendingRectangularSelection { - constructor(owner) { + constructor(owner, mode) { this.owner = owner; + this.mode = mode ?? 'new'; // new, add, subtract this.element = mk_svg('rect.overlay-pending-selection'); this.size_text = mk_svg('text.overlay-edit-tip'); this.owner.svg_group.append(this.element, this.size_text); @@ -54,7 +55,16 @@ export class PendingRectangularSelection { } commit() { - this.owner.add_rect(this.rect); + if (this.mode === 'new') { + this.owner.clear(); + this.owner.add_rect(this.rect); + } + else if (this.mode === 'add') { + this.owner.add_rect(this.rect); + } + else if (this.mode === 'subtract') { + this.owner.subtract_rect(this.rect); + } this.element.remove(); this.size_text.remove(); } @@ -116,8 +126,8 @@ export class Selection { return this.cells.has(this.editor.stored_level.coords_to_scalar(x, y)); } - create_pending() { - return new PendingRectangularSelection(this); + create_pending(mode) { + return new PendingRectangularSelection(this, mode); } add_rect(rect) { @@ -154,18 +164,36 @@ export class Selection { Math.max(this.bbox.bottom, rect.bottom) - this.bbox.y); } - // XXX wait what the hell is this doing here? why would we set_from_rect while floating, vs - // stamping it first? - if (this.floated_element) { - console.log("what the hell is this doing here"); - let tileset = this.editor.renderer.tileset; - this.floated_canvas.width = this.bbox.width * tileset.size_x; - this.floated_canvas.height = this.bbox.height * tileset.size_y; - let foreign_obj = this.floated_element.querySelector('foreignObject'); - foreign_obj.setAttribute('width', this.floated_canvas.width); - foreign_obj.setAttribute('height', this.floated_canvas.height); + this._update_outline(); + } + + subtract_rect(rect) { + let old_cells = this.cells; + this.cells = new Set(this.cells); + + this.editor._do( + () => this._subtract_rect(rect), + () => { + this._set_from_set(old_cells); + }, + false, + ); + } + + _subtract_rect(rect) { + if (this.is_empty) + // Nothing to do + return; + + let stored_level = this.editor.stored_level; + for (let y = rect.top; y < rect.bottom; y++) { + for (let x = rect.left; x < rect.right; x++) { + this.cells.delete(stored_level.coords_to_scalar(x, y)); + } } + // TODO shrink bbox? i guess i only have to check along the edges that the rect intersects? + this._update_outline(); } diff --git a/js/editor/mouseops.js b/js/editor/mouseops.js index 09099b0..3c531c5 100644 --- a/js/editor/mouseops.js +++ b/js/editor/mouseops.js @@ -525,13 +525,21 @@ export class FillOperation extends MouseOperation { // TODO also, delete // FIXME i broke transforms -// FIXME need to subtract from selection too +// FIXME don't show the overlay text until has_moved +// FIXME hide the god damn cursor export class SelectOperation extends MouseOperation { handle_press() { if (this.shift) { - // Extend selection - this.mode = 'extend'; - this.pending_selection = this.editor.selection.create_pending(); + this.mode = 'select'; + if (this.ctrl) { + // Subtract from selection (the normal way is ctrl, but ctrl-shift works even to + // start dragging inside an existing selection) + this.pending_selection = this.editor.selection.create_pending('subtract'); + } + else { + // Extend selection + this.pending_selection = this.editor.selection.create_pending('add'); + } this.update_pending_selection(); } else if (! this.editor.selection.is_empty && @@ -542,9 +550,15 @@ export class SelectOperation extends MouseOperation { this.make_copy = this.ctrl; } else { - // Create new selection - this.mode = 'create'; - this.pending_selection = this.editor.selection.create_pending(); + this.mode = 'select'; + if (this.ctrl) { + // Subtract from selection (must initiate click outside selection, or it'll float) + this.pending_selection = this.editor.selection.create_pending('subtract'); + } + else { + // Create new selection + this.pending_selection = this.editor.selection.create_pending('new'); + } this.update_pending_selection(); } this.has_moved = false; @@ -598,9 +612,6 @@ export class SelectOperation extends MouseOperation { // commit it before doing anything else this.editor.selection.commit_floating(); - if (this.mode === 'create') { - this.editor.selection.clear(); - } this.pending_selection.commit(); } else { diff --git a/style.css b/style.css index fa21a82..9c44b09 100644 --- a/style.css +++ b/style.css @@ -2244,6 +2244,7 @@ svg.level-editor-overlay path.overlay-selection-background { svg.level-editor-overlay path.overlay-selection { stroke: hsla(var(--selected-hue), 10%, 10%, 0.75); fill: hsla(var(--selected-hue), 50%, 75%, 0.375); + fill-rule: evenodd; stroke-width: calc(0.125px / var(--scale)); stroke-dasharray: calc(0.125px / var(--scale)), calc(0.125px / var(--scale)); animation: marching-ants 0.5s linear infinite;