From 5da2cf14db88be00c278b22505002eadac3338c4 Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Mon, 22 Apr 2024 10:22:31 -0600 Subject: [PATCH] Give the adjust tool a live preview (still rough) --- js/editor/mouseops.js | 234 +++++++++++++++++++++--------------------- 1 file changed, 115 insertions(+), 119 deletions(-) diff --git a/js/editor/mouseops.js b/js/editor/mouseops.js index ea70f32..bd23cbe 100644 --- a/js/editor/mouseops.js +++ b/js/editor/mouseops.js @@ -276,6 +276,9 @@ export class MouseOperation { handle_tile_updated(is_bg = false) {} // Called when the mouse leaves the level or viewport while the button is NOT held down handle_leave() {} + // Called when any hover state should be thrown away, due to some external change, just before + // handle_drag or handle_hover is called + handle_refresh() {} } export class PanOperation extends MouseOperation { @@ -800,10 +803,18 @@ export class ForceFloorOperation extends MouseOperation { // fix it if it wasn't there? // TODO gonna need an ice tool too, so maybe i can merge all three with some base thing that tracks // the directions the mouse is moving? or is FF tool too different? +// TODO would be nice if i could add or remove individual tracks with a single click, too. +// and/or pop open the track editor bubble thing? export class TrackOperation extends MouseOperation { handle_press() { // Do nothing to start; we only lay track when the mouse leaves a cell this.entry_direction = null; + + // ...unless... + let tri_x = Math.floor((this.click_frac_cell_x - this.click_cell_x) * 3); + let tri_y = Math.floor((this.click_frac_cell_y - this.click_cell_y) * 3); + // TODO add or remove track on the edge clicked here? hover preview? + // TODO ctrl-mouse2 for the popup? } handle_drag(client_x, client_y, frac_cell_x, frac_cell_y) { // Walk the mouse movement and, for every tile we LEAVE, add a railroad track matching the @@ -1314,6 +1325,26 @@ export class RotateOperation extends MouseOperation { cy: 0.5, r: 0.75, })); + // TODO better preview + /* + let renderer = this.editor.renderer; + this.canvas = mk('canvas', { + width: renderer.tileset.size_x, + height: renderer.tileset.size_y, + }); + // Need an extra here so the translate transform doesn't clobber the scale on the + // foreignObject + this.set_cursor_element(mk_svg('g.overlay-transient', + mk_svg('foreignObject', { + x: 0, + y: 0, + width: this.canvas.width, + height: this.canvas.height, + transform: `scale(${1/renderer.tileset.size_x} ${1/renderer.tileset.size_y})`, + opacity: 0.75, + }, this.canvas), + )); + */ } _find_target_tile(cell) { @@ -1621,10 +1652,34 @@ export class AdjustOperation extends MouseOperation { this.hovered_layer = null; } - _find_target_tile(cell) { + // The real work happens on hover, since we need to actually adjust the tile in order to preview + // it anyway; clicking just commits it. + handle_hover(client_x, client_y, frac_cell_x, frac_cell_y, cell_x, cell_y) { + // TODO hrmm if we undo without moving the mouse then this becomes wrong (even without the + // stuff here) + // TODO uhhh that's true for all kinds of kb shortcuts actually, even for pressing/releasing + // ctrl or shift to change the target. dang + if (cell_x === this.prev_cell_x && cell_y === this.prev_cell_y && + (! this.hovered_edge || this.hovered_edge === this.get_tile_edge()) && + ! this.hover_stale) + { + return; + } + + let cell = this.cell(cell_x, cell_y); + + // Find our target tile, and in most cases, adjust it + this.hover_stale = false; + this.adjusted_tile = null; + this.hovered_edge = null; let top_layer = LAYERS.MAX - 1; let bottom_layer = 0; - if (this.ctrl) { + if (this.ctrl && this.shift) { + // ctrl-shift: explicitly target item + top_layer = LAYERS.item; + bottom_layer = LAYERS.item; + } + else if (this.ctrl) { // ctrl: explicitly target terrain top_layer = LAYERS.terrain; bottom_layer = LAYERS.terrain; @@ -1640,39 +1695,55 @@ export class AdjustOperation extends MouseOperation { continue; // This is kind of like documentation for everything the adjust tool can do I guess - if (TILE_TYPES['transmogrifier']._mogrifications[tile.type.name]) { - // Toggle between related tile types - return [layer, "Mogrify"]; - } if (ADJUST_TILE_TYPES[tile.type.name]) { // Toggle between related tile types - return [layer, ADJUST_TILE_TYPES[tile.type.name].verb]; + let adjustment = ADJUST_TILE_TYPES[tile.type.name]; + //verb = adjustment.verb; + this.adjusted_tile = {...tile, type: TILE_TYPES[adjustment.next]}; + break; } if (tile.type.name === 'logic_gate' && ADJUST_GATE_TYPES[tile.gate_type]) { // Also toggle between related logic gate types - return [layer, "Change"]; + //verb = "Change"; + this.adjusted_tile = {...tile, gate_type: ADJUST_GATE_TYPES[tile.gate_type].next}; + break; } - if (tile.type.name === 'logic_gate' && tile.gate_type === 'counter') { - // Adjust the starting number on a logic gate - return [layer, "Count"]; + let behavior = SPECIAL_TILE_BEHAVIOR[tile.type.name]; + if (behavior && behavior.adjust_forward) { + // Do faux-rotation, which includes incrementing counter gates + //verb = "Adjust"; + this.adjusted_tile = {...tile}; + behavior.adjust_forward(this.adjusted_tile); + break; } if (layer === LAYERS.thin_wall) { // Place or delete individual thin walls - return [layer, "Place"]; + let edge = this.get_tile_edge(); + this.hovered_edge = edge; + this.adjusted_tile = {...tile}; + this.adjusted_tile.edges ^= DIRECTIONS[edge].bit; + break; } - if (tile.type.name === 'frame block') { + if (tile.type.name === 'frame_block') { // Place or delete individual frame block arrows - return [layer, "Place"]; + // TODO this kinda obviates the need for a frame block editor + this.adjusted_tile = {...tile, arrows: new Set(tile.arrows)}; + let edge = this.get_tile_edge(); + this.hovered_edge = edge; + if (this.adjusted_tile.arrows.has(edge)) { + this.adjusted_tile.arrows.delete(edge); + } + else { + this.adjusted_tile.arrows.add(edge); + } + break; } // These are // TODO need a single-click thing to do for - let behavior = SPECIAL_TILE_BEHAVIOR[tile.type.name]; - if (behavior && behavior.adjust_forward) { - // - return [layer, "Adjust"]; - } + // TODO hmm. what do i do with these + /* if (TILES_WITH_PROPS[tile.type.name]) { // Open special tile editors return [layer, "Edit"]; @@ -1681,22 +1752,9 @@ export class AdjustOperation extends MouseOperation { if (ADJUST_SPECIAL[tile.type.name]) { return [layer, "Press"]; } + */ } - - return [null, null]; - } - - handle_hover(client_x, client_y, frac_cell_x, frac_cell_y, cell_x, cell_y) { - // TODO hrmm if we undo without moving the mouse then this becomes wrong (even without the - // stuff here) - // TODO uhhh that's true for all kinds of kb shortcuts actually, even for pressing/releasing - // ctrl or shift to change the target. dang - if (cell_x === this.prev_cell_x && cell_y === this.prev_cell_y) - return; - - let cell = this.cell(cell_x, cell_y); - let [layer, hint] = this._find_target_tile(cell); - this.hovered_layer = layer; + /* if (hint === null) { this.click_hint.classList.remove('--visible'); } @@ -1706,47 +1764,26 @@ export class AdjustOperation extends MouseOperation { this.click_hint.setAttribute('y', cell_y - 0.125); this.click_hint.textContent = hint; } + */ - if (layer === null) { + if (! this.adjusted_tile) { this.cursor_element.classList.remove('--visible'); this.gray_button_preview.classList.remove('--visible'); return; } - let tile = cell[layer]; - /* + // Draw the altered tile on top of everything else this.cursor_element.classList.add('--visible'); - if (layer === LAYERS.terrain) { - this.cursor_element.setAttribute('data-layer', 'terrain'); - } - else if (layer === LAYERS.item) { - this.cursor_element.setAttribute('data-layer', 'item'); - } - else if (layer === LAYERS.actor) { - this.cursor_element.setAttribute('data-layer', 'actor'); - } - else if (layer === LAYERS.thin_wall) { - this.cursor_element.setAttribute('data-layer', 'thin-wall'); - } - */ - - if (cell.filter(t => t).length <= 1) { - // Only one tile, so the canvas is pointless - this.cursor_element.classList.remove('--visible'); - } - else { - // Draw the targeted tile on top of everything else - this.cursor_element.classList.add('--visible'); - let ctx = this.canvas.getContext('2d'); - ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - if (layer !== LAYERS.terrain) { - this.editor.renderer.draw_single_tile_type('floor', null, this.canvas); - } - this.editor.renderer.draw_single_tile_type(tile.type.name, tile, this.canvas); + let ctx = this.canvas.getContext('2d'); + ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + if (this.adjusted_tile.type.layer !== LAYERS.terrain) { + this.editor.renderer.draw_single_tile_type('floor', null, this.canvas); } + this.editor.renderer.draw_single_tile_type( + this.adjusted_tile.type.name, this.adjusted_tile, this.canvas); // Special previewing behavior - if (tile.type.name === 'button_gray') { + if (this.adjusted_tile.type.name === 'button_gray') { this.cursor_element.classList.remove('--visible'); this.gray_button_preview.classList.add('--visible'); let gx0 = Math.max(0, cell_x - 2); @@ -1793,63 +1830,16 @@ export class AdjustOperation extends MouseOperation { handle_press() { let cell = this.cell(this.prev_cell_x, this.prev_cell_y); - let tile = cell[this.hovered_layer]; - if (! tile) - return; - let behavior = SPECIAL_TILE_BEHAVIOR[tile.type.name]; - // Same order as _find_target_tile - if (TILE_TYPES['transmogrifier']._mogrifications[tile.type.name]) { - // Toggle between related tile types - tile.type = TILE_TYPES[TILE_TYPES['transmogrifier']._mogrifications[tile.type.name]]; - this.editor.place_in_cell(cell, tile); - this.editor.commit_undo(); - } - else if (ADJUST_TILE_TYPES[tile.type.name]) { - // Toggle between related tile types - // TODO can you go backwards any more, or no? - let toggled = ADJUST_TILE_TYPES[tile.type.name].next; - tile.type = TILE_TYPES[toggled]; - this.editor.place_in_cell(cell, tile); - this.editor.commit_undo(); - } - else if (tile.type.name === 'logic_gate' && ADJUST_GATE_TYPES[tile.gate_type]) { - // Also toggle between related logic gate types - let toggled = ADJUST_GATE_TYPES[tile.gate_type].next; - tile.gate_type = toggled; - this.editor.place_in_cell(cell, tile); - this.editor.commit_undo(); - } - else if (tile.type.name === 'logic_gate' && tile.gate_type === 'counter') { - // Adjust the starting number on a logic gate - // TODO is this in adjust_forward or...? - } - else if (this.hovered_layer === LAYERS.thin_wall) { - // Place or delete individual thin walls - // XXX don't allow deleting ALL the thin walls...?? - let bit = DIRECTIONS[this.get_tile_edge()].bit; - tile.edges ^= bit; - this.editor.place_in_cell(cell, tile); - this.editor.commit_undo(); - } - else if (tile.type.name === 'frame_block') { - // Place or delete individual frame block arrows - let edge = this.get_tile_edge(); - tile.arrows = new Set(tile.arrows); - if (tile.arrows.has(edge)) { - tile.arrows.delete(edge); - } - else { - tile.arrows.add(edge); - } - this.editor.place_in_cell(cell, tile); - this.editor.commit_undo(); - } - else if (behavior && behavior.adjust_forward) { - behavior.adjust_forward(tile); - this.editor.place_in_cell(cell, tile); + if (this.adjusted_tile) { + this.editor.place_in_cell(cell, this.adjusted_tile); this.editor.commit_undo(); + // The tile has changed, so invalidate our hover + // TODO should the editor do this automatically, since the cell changed? + this.handle_refresh(); + this.handle_hover(null, null, null, null, this.prev_cell_x, this.prev_cell_y); } + /* else if (ADJUST_SPECIAL[tile.type.name]) { ADJUST_SPECIAL[tile.type.name](this.editor, tile, cell); this.editor.commit_undo(); @@ -1860,7 +1850,13 @@ export class AdjustOperation extends MouseOperation { this.editor.open_tile_prop_overlay( tile, cell, this.editor.renderer.get_cell_rect(cell.x, cell.y)); } + */ } + + handle_refresh() { + this.hover_stale = true; + } + // Adjust tool doesn't support dragging // TODO should it? // TODO if it does then it should end as soon as you spawn a popup