Editor: Teach the adjust tool to edit individual tiles
This commit is contained in:
parent
560a89cfd3
commit
0d376e003e
@ -198,6 +198,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<nav class="controls">
|
||||
<div id="editor-tile">
|
||||
<!-- tools go here -->
|
||||
</div>
|
||||
<div id="editor-toolbar">
|
||||
<!-- tools go here -->
|
||||
</div>
|
||||
|
||||
@ -54,6 +54,8 @@ export class Overlay {
|
||||
overlay.addEventListener('click', ev => {
|
||||
this.close();
|
||||
});
|
||||
|
||||
return overlay;
|
||||
}
|
||||
|
||||
close() {
|
||||
@ -61,6 +63,16 @@ export class Overlay {
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay styled like a popup of some sort
|
||||
export class TransientOverlay extends Overlay {
|
||||
open() {
|
||||
// TODO i don't like how vaguely arbitrary this feels.
|
||||
let overlay = super.open();
|
||||
overlay.classList.add('--transient');
|
||||
return overlay;
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay styled like a dialog box
|
||||
export class DialogOverlay extends Overlay {
|
||||
constructor(conductor) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { DIRECTIONS, TICS_PER_SECOND } from './defs.js';
|
||||
import * as c2g from './format-c2g.js';
|
||||
import { PrimaryView, DialogOverlay } from './main-base.js';
|
||||
import { PrimaryView, TransientOverlay, DialogOverlay } from './main-base.js';
|
||||
import CanvasRenderer from './renderer-canvas.js';
|
||||
import TILE_TYPES from './tiletypes.js';
|
||||
import { SVG_NS, mk, mk_svg, walk_grid } from './util.js';
|
||||
@ -266,7 +266,17 @@ const ADJUST_TOGGLES = {
|
||||
};
|
||||
class AdjustOperation extends MouseOperation {
|
||||
start() {
|
||||
let cell = this.editor.stored_level.cells[this.gy1][this.gx1];
|
||||
let cell = this.cell(this.gx1, this.gy1);
|
||||
if (this.modifier === 'ctrl') {
|
||||
for (let tile of cell) {
|
||||
if (tile.type.name === 'floor_letter') {
|
||||
// TODO use the tile's bbox, not the mouse position
|
||||
this.editor.open_tile_prop_overlay(tile, this.mx0, this.my0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (let tile of cell) {
|
||||
// Toggle tiles that go in obvious pairs
|
||||
let other = ADJUST_TOGGLES[tile.type.name];
|
||||
@ -282,6 +292,7 @@ class AdjustOperation extends MouseOperation {
|
||||
}
|
||||
// Adjust tool doesn't support dragging
|
||||
// TODO should it?
|
||||
// TODO if it does then it should end as soon as you spawn a popup
|
||||
}
|
||||
|
||||
// FIXME currently allows creating outside the map bounds and moving beyond the right/bottom, sigh
|
||||
@ -472,9 +483,10 @@ const EDITOR_TOOLS = {
|
||||
pencil: {
|
||||
icon: 'icons/tool-pencil.png',
|
||||
name: "Pencil",
|
||||
desc: "Draw individual tiles",
|
||||
desc: "Click to draw. Right click to erase. Hold Shift to affect all layers. Ctrl-click to eyedrop.",
|
||||
op1: PencilOperation,
|
||||
//op2: EraseOperation,
|
||||
//hover: show current selection under cursor
|
||||
},
|
||||
line: {
|
||||
// TODO not implemented
|
||||
@ -503,7 +515,7 @@ const EDITOR_TOOLS = {
|
||||
adjust: {
|
||||
icon: 'icons/tool-adjust.png',
|
||||
name: "Adjust",
|
||||
desc: "Toggle blocks and rotate actors",
|
||||
desc: "Click to rotate actor or toggle terrain. Right click to rotate in reverse. Hold Shift to always affect terrain. Ctrl-click to edit properties of complex tiles (wires, railroads, hints, etc.)",
|
||||
op1: AdjustOperation,
|
||||
},
|
||||
connect: {
|
||||
@ -602,6 +614,49 @@ const EDITOR_PALETTE = [{
|
||||
],
|
||||
}];
|
||||
|
||||
// FIXME could very much stand to have a little animation when appearing
|
||||
class LetterTileEditor extends TransientOverlay {
|
||||
constructor(conductor) {
|
||||
let root = mk('form.editor-popup-tile-editor');
|
||||
root.append(mk('span.popup-chevron'));
|
||||
super(conductor, root);
|
||||
this.tile = null;
|
||||
|
||||
let list = mk('ol.editor-letter-tile-picker');
|
||||
root.append(list);
|
||||
this.glyph_elements = {};
|
||||
for (let c = 32; c < 128; c++) {
|
||||
let glyph = String.fromCharCode(c);
|
||||
let input = mk('input', {type: 'radio', name: 'glyph', value: glyph});
|
||||
this.glyph_elements[glyph] = input;
|
||||
let item = mk('li', mk('label', input, mk('span.-glyph', glyph)));
|
||||
list.append(item);
|
||||
}
|
||||
|
||||
list.addEventListener('change', ev => {
|
||||
let glyph = this.root.elements['glyph'].value;
|
||||
if (this.tile) {
|
||||
this.tile.ascii_code = glyph.charCodeAt(0);
|
||||
// FIXME should be able to mark tiles as dirty, also this is sure a mouthful
|
||||
this.conductor.editor.renderer.draw();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
edit_tile(tile) {
|
||||
this.tile = tile;
|
||||
this.root.elements['glyph'].value = String.fromCharCode(tile.ascii_code);
|
||||
}
|
||||
|
||||
static configure_tile_defaults(tile) {
|
||||
tile.ascii_code = 32;
|
||||
}
|
||||
}
|
||||
|
||||
const EDITOR_TILES_WITH_PROPS = {
|
||||
floor_letter: LetterTileEditor,
|
||||
};
|
||||
|
||||
export class Editor extends PrimaryView {
|
||||
constructor(conductor) {
|
||||
super(conductor, document.body.querySelector('main#editor'));
|
||||
@ -874,6 +929,41 @@ export class Editor extends PrimaryView {
|
||||
cell.sort((a, b) => a.type.draw_layer - b.type.draw_layer);
|
||||
}
|
||||
|
||||
open_tile_prop_overlay(tile, x0, y0) {
|
||||
this.cancel_mouse_operation();
|
||||
let overlay_class = EDITOR_TILES_WITH_PROPS[tile.type.name];
|
||||
let overlay = new overlay_class(this.conductor);
|
||||
overlay.edit_tile(tile);
|
||||
overlay.open();
|
||||
|
||||
// FIXME move this into TransientOverlay or some other base class
|
||||
let root = overlay.root;
|
||||
// Vertical position: either above or below, preferring the side that has more space
|
||||
if (y0 > document.body.clientHeight / 2) {
|
||||
// Above
|
||||
root.classList.add('--above');
|
||||
root.style.top = `${y0 - root.offsetHeight}px`;
|
||||
}
|
||||
else {
|
||||
// Below
|
||||
root.classList.remove('--above');
|
||||
root.style.top = `${y0}px`;
|
||||
}
|
||||
// Horizontal position: centered, but kept within the screen
|
||||
let left;
|
||||
let margin = 8; // prefer to not quite touch the edges
|
||||
let halfwidth = root.offsetWidth / 2 + margin;
|
||||
if (document.body.clientWidth / 2 < halfwidth) {
|
||||
// It doesn't fit on the screen at all, so there's nothing we can do; just center it
|
||||
left = (document.body.clientWidth - root.offsetWidth) / 2;
|
||||
}
|
||||
else {
|
||||
left = Math.max(margin, Math.min(document.body.clientWidth - halfwidth, x0 - halfwidth));
|
||||
}
|
||||
root.style.left = `${left}px`;
|
||||
root.style.setProperty('--chevron-offset', `${x0 - left}px`);
|
||||
}
|
||||
|
||||
cancel_mouse_operation() {
|
||||
if (this.mouse_op) {
|
||||
this.mouse_op.do_abort();
|
||||
|
||||
@ -328,6 +328,7 @@ const TILE_TYPES = {
|
||||
on_ready(me) {
|
||||
// If there's already an actor on top of us, assume it entered the way it's already
|
||||
// facing (which may be illegal, in which case it can't leave)
|
||||
// FIXME wrong! > Yeah, so in the high byte, the low nibble encodes the active track. What's missing is that the high nibble encodes a direction value, which is required when a mob starts out on top of the track: it represents the direction the mob was going when it entered the tile, which controls which way it can go on the track.
|
||||
let actor = me.cell.get_actor();
|
||||
if (actor) {
|
||||
me.entered_direction = actor.direction;
|
||||
|
||||
@ -24,7 +24,9 @@ function _mk(el, children) {
|
||||
export function mk(tag_selector, ...children) {
|
||||
let [tag, ...classes] = tag_selector.split('.');
|
||||
let el = document.createElement(tag);
|
||||
el.classList = classes.join(' ');
|
||||
if (classes.length > 0) {
|
||||
el.classList = classes.join(' ');
|
||||
}
|
||||
return _mk(el, children);
|
||||
}
|
||||
|
||||
@ -32,7 +34,9 @@ export const SVG_NS = 'http://www.w3.org/2000/svg';
|
||||
export function mk_svg(tag_selector, ...children) {
|
||||
let [tag, ...classes] = tag_selector.split('.');
|
||||
let el = document.createElementNS(SVG_NS, tag);
|
||||
el.classList = classes.join(' ');
|
||||
if (classes.length > 0) {
|
||||
el.classList = classes.join(' ');
|
||||
}
|
||||
return _mk(el, children);
|
||||
}
|
||||
|
||||
|
||||
62
style.css
62
style.css
@ -143,6 +143,11 @@ svg.svg-icon {
|
||||
right: 0;
|
||||
background: #fff4;
|
||||
}
|
||||
.overlay.--transient {
|
||||
align-items: start;
|
||||
justify-content: start;
|
||||
background: none;
|
||||
}
|
||||
.dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -1011,3 +1016,60 @@ main.--has-demo .demo-controls {
|
||||
z-index: 1;
|
||||
box-shadow: 0 0 0 1px black, 0 0 0 3px white;
|
||||
}
|
||||
|
||||
/* Mini editors for specific tiles with complex properties */
|
||||
/* FIXME should this stuff be on an overlay container class? */
|
||||
form.editor-popup-tile-editor {
|
||||
position: relative;
|
||||
padding: 0.5em;
|
||||
color: black;
|
||||
background: white;
|
||||
border: 1px solid black;
|
||||
box-shadow: 0 2px 4px #0004;
|
||||
|
||||
margin-top: 1em;
|
||||
|
||||
--chevron-offset: 0px;
|
||||
}
|
||||
form.editor-popup-tile-editor.--above {
|
||||
margin-top: -1em;
|
||||
}
|
||||
.popup-chevron {
|
||||
position: absolute;
|
||||
border: 1em solid transparent;
|
||||
left: var(--chevron-offset);
|
||||
margin-left: -1em;
|
||||
|
||||
top: -1em;
|
||||
border-top: none;
|
||||
border-bottom-color: white;
|
||||
filter: drop-shadow(0 -1px 0 black);
|
||||
}
|
||||
form.editor-popup-tile-editor.--above .popup-chevron {
|
||||
top: auto;
|
||||
bottom: -1em;
|
||||
border: 1em solid transparent;
|
||||
border-bottom: none;
|
||||
border-top-color: white;
|
||||
filter: drop-shadow(0 1px 0 black);
|
||||
}
|
||||
/* Letter floor tiles, which let you pick the character to use; show them as a grid. Note the
|
||||
* characters are preceded by radio buttons, which we hide for simplicity */
|
||||
ol.editor-letter-tile-picker {
|
||||
display: grid;
|
||||
grid: auto-flow 1.5em / repeat(16, 1.5em);
|
||||
text-align: center;
|
||||
font-family: monospace;
|
||||
}
|
||||
ol.editor-letter-tile-picker label,
|
||||
ol.editor-letter-tile-picker .-glyph {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
ol.editor-letter-tile-picker input[type=radio] {
|
||||
display: none;
|
||||
}
|
||||
ol.editor-letter-tile-picker input[type=radio]:checked + .-glyph {
|
||||
background: hsl(225, 75%, 90%);
|
||||
outline: 2px solid hsl(225, 75%, 80%);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user