Make mouse operations always exist, not only while clicking

This allows for multi-eyedrop (where right-clicking the same cell cycles
through everything in that cell) to finally work.

Also fixes #72, I think.
This commit is contained in:
Eevee (Evelyn Woods) 2024-04-16 05:23:56 -06:00
parent 933d20d559
commit bef5550a95
2 changed files with 54 additions and 66 deletions

View File

@ -210,6 +210,7 @@ export class Editor extends PrimaryView {
// Level canvas and mouse handling // Level canvas and mouse handling
this.mouse_coords = null; this.mouse_coords = null;
this.mouse_ops = [null, new mouseops.PanOperation(this, 1), null]; // left, middle, right
this.mouse_op = null; this.mouse_op = null;
this.viewport_el.addEventListener('mousedown', ev => { this.viewport_el.addEventListener('mousedown', ev => {
this.mouse_coords = [ev.clientX, ev.clientY]; this.mouse_coords = [ev.clientX, ev.clientY];
@ -223,36 +224,10 @@ export class Editor extends PrimaryView {
button = 0; button = 0;
} }
let op_type = null; this.set_mouse_button(button);
if (button === 0) { if (this.mouse_op) {
// Left button: activate tool this.mouse_op.do_press(ev);
op_type = TOOLS[this.current_tool].op1;
} }
else if (button === 1) {
// Middle button: always pan
op_type = mouseops.PanOperation;
// TODO how should this impact the hover?
}
else if (button === 2) {
// Right button: activate tool's alt mode
op_type = TOOLS[this.current_tool].op2;
}
if (! op_type)
return;
if (this.mouse_op && this.mouse_op instanceof op_type &&
this.mouse_op.physical_button === button)
{
// Don't replace with the same operation!
}
else {
if (this.mouse_op) {
this.mouse_op.do_destroy();
}
this.mouse_op = new op_type(this, ev.clientX, ev.clientY, ev.button, button);
}
this.mouse_op.do_press(ev);
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
@ -284,9 +259,9 @@ export class Editor extends PrimaryView {
this.statusbar_cursor.textContent = ``; this.statusbar_cursor.textContent = ``;
} }
if (! this.mouse_op) if (this.mouse_op) {
return; this.mouse_op.do_move(ev);
this.mouse_op.do_move(ev); }
}); });
this.actual_viewport_el.addEventListener('mouseleave', () => { this.actual_viewport_el.addEventListener('mouseleave', () => {
if (this.mouse_op) { if (this.mouse_op) {
@ -302,10 +277,7 @@ export class Editor extends PrimaryView {
ev.preventDefault(); ev.preventDefault();
this.mouse_op.do_commit(); this.mouse_op.do_commit();
// If this was an op for a different button, switch back to the primary this.set_mouse_button(0);
if (this.mouse_op.physical_button !== 0) {
this.init_default_mouse_op();
}
}); });
// Disable context menu, which interferes with right-click tools // Disable context menu, which interferes with right-click tools
this.viewport_el.addEventListener('contextmenu', ev => { this.viewport_el.addEventListener('contextmenu', ev => {
@ -1167,17 +1139,31 @@ export class Editor extends PrimaryView {
} }
this.current_tool = tool; this.current_tool = tool;
this.tool_button_els[this.current_tool].classList.add('-selected'); this.tool_button_els[this.current_tool].classList.add('-selected');
this.init_default_mouse_op();
// Left button: activate tool
this._init_mouse_op(0, this.current_tool && TOOLS[this.current_tool].op1);
// Right button: activate tool's alt mode
this._init_mouse_op(2, this.current_tool && TOOLS[this.current_tool].op2);
this.set_mouse_button(0);
}
_init_mouse_op(button, op_type) {
if (this.mouse_ops[button] && op_type && this.mouse_ops[button] instanceof op_type)
// Don't recreate the same type of mouse operation
return;
if (this.mouse_ops[button]) {
this.mouse_ops[button].do_destroy();
this.mouse_ops[button] = null;
}
if (op_type) {
this.mouse_ops[button] = new op_type(this, button);
}
} }
init_default_mouse_op() { set_mouse_button(button) {
if (this.mouse_op) { this.mouse_op = this.mouse_ops[button];
this.mouse_op.do_destroy();
this.mouse_op = null;
}
if (this.current_tool && TOOLS[this.current_tool].op1) {
this.mouse_op = new TOOLS[this.current_tool].op1(this, ...(this.mouse_coords ?? [0, 0]), 0); // FIXME?
}
} }
show_palette_tooltip(key) { show_palette_tooltip(key) {
@ -1330,8 +1316,10 @@ export class Editor extends PrimaryView {
ctx.clearRect(0, 0, this.fg_tile_el.width, this.fg_tile_el.height); ctx.clearRect(0, 0, this.fg_tile_el.width, this.fg_tile_el.height);
this.renderer.draw_single_tile_type( this.renderer.draw_single_tile_type(
this.fg_tile.type.name, this.fg_tile, this.fg_tile_el); this.fg_tile.type.name, this.fg_tile, this.fg_tile_el);
if (this.mouse_op) { for (let mouse_op of this.mouse_ops) {
this.mouse_op.handle_tile_updated(); if (mouse_op) {
mouse_op.handle_tile_updated();
}
} }
} }
@ -1340,8 +1328,10 @@ export class Editor extends PrimaryView {
ctx.clearRect(0, 0, this.bg_tile_el.width, this.bg_tile_el.height); ctx.clearRect(0, 0, this.bg_tile_el.width, this.bg_tile_el.height);
this.renderer.draw_single_tile_type( this.renderer.draw_single_tile_type(
this.bg_tile.type.name, this.bg_tile, this.bg_tile_el); this.bg_tile.type.name, this.bg_tile, this.bg_tile_el);
if (this.mouse_op) { for (let mouse_op of this.mouse_ops) {
this.mouse_op.handle_tile_updated(true); if (mouse_op) {
mouse_op.handle_tile_updated(true);
}
} }
} }

View File

@ -10,28 +10,23 @@ import { TILES_WITH_PROPS } from './tile-overlays.js';
const MOUSE_BUTTON_MASKS = [1, 4, 2]; // MouseEvent.button/buttons are ordered differently const MOUSE_BUTTON_MASKS = [1, 4, 2]; // MouseEvent.button/buttons are ordered differently
export class MouseOperation { export class MouseOperation {
constructor(editor, client_x, client_y, physical_button) { constructor(editor, physical_button) {
this.editor = editor; this.editor = editor;
this.is_held = false; this.is_held = false;
this.physical_button = physical_button; this.physical_button = physical_button;
this.alt_mode = physical_button !== 0; this.alt_mode = physical_button !== 0;
this.ctrl = false; this.ctrl = false;
this.shift = false; this.shift = false;
//this._update_modifiers(ev); // FIXME how do i get this initially??
let [frac_cell_x, frac_cell_y] = this.editor.renderer.real_cell_coords_from_event({clientX: client_x, clientY: client_y});
let cell_x = Math.floor(frac_cell_x);
let cell_y = Math.floor(frac_cell_y);
// Client coordinates of the previous mouse event // Client coordinates of the previous mouse event
this.prev_client_x = client_x; this.prev_client_x = null;
this.prev_client_y = client_y; this.prev_client_y = null;
// Cell coordinates // Cell coordinates
this.prev_cell_x = cell_x; this.prev_cell_x = null;
this.prev_cell_y = cell_y; this.prev_cell_y = null;
// Fractional cell coordinates // Fractional cell coordinates
this.prev_frac_cell_x = frac_cell_x; this.prev_frac_cell_x = null;
this.prev_frac_cell_y = frac_cell_y; this.prev_frac_cell_y = null;
// Same as above, but for the most recent click (so drag ops know where they started) // Same as above, but for the most recent click (so drag ops know where they started)
this.click_client_x = null; this.click_client_x = null;
@ -40,10 +35,6 @@ export class MouseOperation {
this.click_cell_y = null; this.click_cell_y = null;
this.click_frac_cell_x = null; this.click_frac_cell_x = null;
this.click_frac_cell_y = null; this.click_frac_cell_y = null;
// Start out with a hover effect
// FIXME no good, subclass hasn't finished setting itself up yet
//this.do_move({clientX: client_x, clientY: client_y});
} }
cell(x, y) { cell(x, y) {
@ -207,6 +198,8 @@ export class PanOperation extends MouseOperation {
} }
} }
// FIXME handle moving the mouse while the button is down; should continuously eyedrop
// (seems like that /should/ work...)
export class EyedropOperation extends MouseOperation { export class EyedropOperation extends MouseOperation {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
@ -296,10 +289,13 @@ export class PencilOperation extends MouseOperation {
handle_press(x, y) { handle_press(x, y) {
this.draw_in_cell(x, y); this.draw_in_cell(x, y);
} }
handle_drag(client_x, client_y, frac_cell_x, frac_cell_y, _cell_x, _cell_y) { handle_drag(client_x, client_y, frac_cell_x, frac_cell_y, cell_x, cell_y) {
for (let [x, y] of this.iter_touched_cells(frac_cell_x, frac_cell_y)) { for (let [x, y] of this.iter_touched_cells(frac_cell_x, frac_cell_y)) {
this.draw_in_cell(x, y); this.draw_in_cell(x, y);
} }
// Also update the preview tile position
this.handle_hover(client_x, client_y, frac_cell_x, frac_cell_y, cell_x, cell_y);
} }
draw_in_cell(x, y) { draw_in_cell(x, y) {
@ -346,6 +342,8 @@ export class PencilOperation extends MouseOperation {
// toggled) // toggled)
// - right-click to pick, same logic as pencil (which needs improving) // - right-click to pick, same logic as pencil (which needs improving)
// - ctrl-click to erase // - ctrl-click to erase
// - wait, no. ctrl to like, fill the terrain layer regardless of the current tile's layer? atm
// you can't flood with an item usefully, it just fills the whole level
// - reset the preview after a fill? is that ever necessary? // - reset the preview after a fill? is that ever necessary?
export class FillOperation extends MouseOperation { export class FillOperation extends MouseOperation {
constructor(...args) { constructor(...args) {