Editor: Add selected tile, tool help, and hint editing; clean up toolbar style

This commit is contained in:
Eevee (Evelyn Woods) 2020-12-02 11:34:46 -07:00
parent 0d376e003e
commit ec5d9f7b12
6 changed files with 208 additions and 86 deletions

View File

@ -199,18 +199,11 @@
</div>
<nav class="controls">
<div id="editor-tile">
<!-- tools go here -->
</div>
<div id="editor-toolbar">
<!-- tools go here -->
</div>
<div class="-buttons">
<button id="editor-share-url" type="button">Share?</button>
<button id="editor-toggle-green" type="button">Toggle green objects</button>
</div>
<div id="editor-tool-help">
<strong>Pencil</strong><span>Select a tile and draw with the left mouse button. Erase with the right mouse button.</span>
</div>
<!--
<p style>
Tip: Right click to color drop.<br>

View File

@ -0,0 +1,90 @@
import { TransientOverlay } from './main-base.js';
import { mk } from './util.js';
// FIXME could very much stand to have a little animation when appearing
class TileEditorOverlay extends TransientOverlay {
constructor(conductor) {
let root = mk('form.editor-popup-tile-editor');
root.append(mk('span.popup-chevron'));
super(conductor, root);
this.editor = conductor.editor;
this.tile = null;
}
edit_tile(tile) {
this.tile = tile;
}
static configure_tile_defaults(tile) {
// FIXME maybe this should be on the tile type, so it functions as documentation there?
}
}
class LetterTileEditor extends TileEditorOverlay {
constructor(conductor) {
super(conductor);
let list = mk('ol.editor-letter-tile-picker');
this.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) {
super.edit_tile(tile);
this.root.elements['glyph'].value = String.fromCharCode(tile.ascii_code);
}
static configure_tile_defaults(tile) {
tile.ascii_code = 32;
}
}
class HintTileEditor extends TileEditorOverlay {
constructor(conductor) {
super(conductor);
this.text = mk('textarea.editor-hint-tile-text');
this.root.append(this.text);
this.text.addEventListener('input', ev => {
if (this.tile) {
this.tile.specific_hint = this.text.value;
}
});
}
edit_tile(tile) {
super.edit_tile(tile);
this.text.value = tile.specific_hint ?? "";
}
static configure_tile_defaults(tile) {
tile.specific_hint = "";
}
}
export const TILES_WITH_PROPS = {
floor_letter: LetterTileEditor,
hint: HintTileEditor,
// TODO various wireable tiles
// TODO initial value of counter
// TODO cloner arrows
// TODO railroad parts
// TODO later, custom floor/wall selection
};

View File

@ -1,4 +1,5 @@
import { DIRECTIONS, TICS_PER_SECOND } from './defs.js';
import { TILES_WITH_PROPS } from './editor-tile-overlays.js';
import * as c2g from './format-c2g.js';
import { PrimaryView, TransientOverlay, DialogOverlay } from './main-base.js';
import CanvasRenderer from './renderer-canvas.js';
@ -150,16 +151,14 @@ class PencilOperation extends DrawOperation {
if (this.modifier === 'ctrl') {
let cell = this.cell(x, y);
if (cell) {
// FIXME the selected tile should actually be a template
// FIXME this doesn't update the ui
this.editor.palette_selection = cell[cell.length - 1].type.name;
this.editor.select_palette(cell[cell.length - 1]);
}
}
else if (this.modifier === 'shift') {
// Aggressive mode: erase whatever's already in the cell
let cell = this.cell(x, y);
cell.length = 0;
let type = TILE_TYPES[this.editor.palette_selection];
let type = this.editor.palette_selection.type;
if (type.draw_layer !== 0) {
cell.push({type: TILE_TYPES.floor});
}
@ -269,7 +268,7 @@ class AdjustOperation extends MouseOperation {
let cell = this.cell(this.gx1, this.gy1);
if (this.modifier === 'ctrl') {
for (let tile of cell) {
if (tile.type.name === 'floor_letter') {
if (TILES_WITH_PROPS[tile.type.name] !== undefined) {
// TODO use the tile's bbox, not the mouse position
this.editor.open_tile_prop_overlay(tile, this.mx0, this.my0);
break;
@ -483,7 +482,7 @@ const EDITOR_TOOLS = {
pencil: {
icon: 'icons/tool-pencil.png',
name: "Pencil",
desc: "Click to draw. Right click to erase. Hold Shift to affect all layers. Ctrl-click to eyedrop.",
desc: "Place, erase, and select tiles.\nLeft click: draw\nRight click: erase\nShift: Replace all layers\nCtrl-click: eyedrop",
op1: PencilOperation,
//op2: EraseOperation,
//hover: show current selection under cursor
@ -509,13 +508,13 @@ const EDITOR_TOOLS = {
'force-floors': {
icon: 'icons/tool-force-floors.png',
name: "Force floors",
desc: "Draw force floors in the direction you draw",
desc: "Draw force floors following the cursor.",
op1: ForceFloorOperation,
},
adjust: {
icon: 'icons/tool-adjust.png',
name: "Adjust",
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.)",
desc: "Edit existing tiles.\nLeft click: rotate actor or toggle terrain\nRight click: rotate or toggle in reverse\nShift: always target terrain\nCtrl-click: edit properties of complex tiles\n(wires, railroads, hints, etc.)",
op1: AdjustOperation,
},
connect: {
@ -534,7 +533,7 @@ const EDITOR_TOOLS = {
icon: 'icons/tool-camera.png',
name: "Camera",
desc: "Draw and edit custom camera regions",
help: "Draw and edit camera regions. Right-click to erase a region. When the player is within a camera region, the camera will avoid showing anything outside that region. LL only.",
help: "Draw and edit camera regions.\n(LL only. When the player is within a camera region,\nthe camera stays locked inside it.)\nLeft click: create or edit a region\nRight click: erase a region",
op1: CameraOperation,
op2: CameraEraseOperation,
},
@ -614,48 +613,6 @@ 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) {
@ -758,7 +715,17 @@ export class Editor extends PrimaryView {
});
// Toolbox
let toolbox = mk('div.icon-button-set')
// Selected tile
this.selected_tile_el = this.root.querySelector('.controls #editor-tile');
this.selected_tile_el.addEventListener('click', ev => {
if (this.palette_selection && TILES_WITH_PROPS[this.palette_selection.type.name]) {
// FIXME use tile bounds
// FIXME redraw the tile after editing
this.open_tile_prop_overlay(this.palette_selection, ev.clientX, ev.clientY);
}
});
// Tools themselves
let toolbox = mk('div.icon-button-set', {id: 'editor-toolbar'});
this.root.querySelector('.controls').append(toolbox);
this.tool_button_els = {};
for (let toolname of EDITOR_TOOL_ORDER) {
@ -771,8 +738,8 @@ export class Editor extends PrimaryView {
mk('img', {
src: tooldef.icon,
alt: tooldef.name,
title: `${tooldef.name}: ${tooldef.desc}`,
}),
mk('div.-help', mk('h3', tooldef.name), tooldef.desc),
);
this.tool_button_els[toolname] = button;
toolbox.append(button);
@ -874,16 +841,40 @@ export class Editor extends PrimaryView {
this.tool_button_els[this.current_tool].classList.add('-selected');
}
select_palette(name) {
if (name === this.palette_selection)
return;
select_palette(name_or_tile) {
let name, tile;
if (typeof name_or_tile === 'string') {
name = name_or_tile;
if (this.palette_selection && name === this.palette_selection.type.name)
return;
tile = { type: TILE_TYPES[name] };
if (tile.type.is_actor) {
tile.direction = 'south';
}
if (TILES_WITH_PROPS[name]) {
TILES_WITH_PROPS[name].configure_tile_defaults(tile);
}
}
else {
tile = Object.assign({}, name_or_tile);
name = tile.type.name;
}
// FIXME should redraw in an existing canvas
this.selected_tile_el.textContent = '';
// FIXME should draw the actual tile!!
this.selected_tile_el.append(this.renderer.create_tile_type_canvas(name, tile));
if (this.palette_selection) {
this.palette[this.palette_selection].classList.remove('--selected');
let entry = this.palette[this.palette_selection.type.name];
if (entry) {
entry.classList.remove('--selected');
}
}
this.palette_selection = name;
if (this.palette_selection) {
this.palette[this.palette_selection].classList.add('--selected');
this.palette_selection = tile;
if (this.palette[name]) {
this.palette[name].classList.add('--selected');
}
// Some tools obviously don't work with a palette selection, in which case changing tiles
@ -906,32 +897,28 @@ export class Editor extends PrimaryView {
}
}
place_in_cell(x, y, name) {
place_in_cell(x, y, tile) {
// TODO weird api?
if (! name)
if (! tile)
return;
let type = TILE_TYPES[name];
let direction;
if (type.is_actor) {
direction = 'south';
}
let cell = this.stored_level.cells[y][x];
// Replace whatever's on the same layer
// TODO probably not the best heuristic yet, since i imagine you can
// combine e.g. the tent with thin walls
for (let i = cell.length - 1; i >= 0; i--) {
if (cell[i].type.draw_layer === type.draw_layer) {
if (cell[i].type.draw_layer === tile.type.draw_layer) {
cell.splice(i, 1);
}
}
cell.push({type, direction});
cell.push(Object.assign({}, tile));
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];
// FIXME keep these around, don't recreate them constantly
let overlay_class = TILES_WITH_PROPS[tile.type.name];
let overlay = new overlay_class(this.conductor);
overlay.edit_tile(tile);
overlay.open();

View File

@ -202,11 +202,11 @@ export class CanvasRenderer {
}
}
create_tile_type_canvas(name) {
create_tile_type_canvas(name, tile = null) {
let canvas = mk('canvas', {width: this.tileset.size_x, height: this.tileset.size_y});
let ctx = canvas.getContext('2d');
this.tileset.draw_type(name, null, 0, (sx, sy, dx = 0, dy = 0, w = 1, h = w) =>
this.blit(ctx, sx, sy, dx, dy, w, h));
this.tileset.draw_type(name, tile, 0, (tx, ty, mx = 0, my = 0, mw = 1, mh = mw, mdx = mx, mdy = my) =>
this.blit(ctx, tx + mx, ty + my, mdx, mdy, mw, mh));
return canvas;
}
}

View File

@ -1147,7 +1147,7 @@ export class Tileset {
// Special behavior for special objects
// TODO? hardcode this less?
if (name === 'floor_letter') {
if (name === 'floor_letter' && tile) {
let n = tile.ascii_code - 32;
let scale = 0.5;
let sx, sy;

View File

@ -949,22 +949,64 @@ main.--has-demo .demo-controls {
}
#editor .controls {
/* TODO with the hint area gone i don't think this needs to be a grid? could just flex */
grid-area: controls;
display: grid;
grid:
"toolbar layer direction menu" auto
"hint hint hint hint" 1.5em
"tile toolbar layer direction . menu" auto
/ auto auto auto auto 1fr auto
;
align-items: center;
column-gap: 1em;
}
#editor .controls #editor-tile {
grid-area: tile;
}
#editor .controls #editor-tile canvas {
display: block;
}
#editor .controls #editor-toolbar {
grid-area: toolbar;
}
#editor-toolbar .-help {
opacity: 0;
visibility: hidden;
z-index: 1;
position: absolute;
margin-top: -0.25em;
margin-left: -0.5em;
padding: 0.33em 0.75em;
pointer-events: none;
border: 1px solid black;
white-space: pre-wrap;
line-height: 1.5;
text-transform: none;
text-align: left;
background: hsl(225, 10%, 20%);
box-shadow: 0 1px 2px 1px #0004;
transition-property: margin-top, opacity, visibility;
transition-timing-function: ease-out;
transition-duration: 0.125s, 0.125s, 0s;
transition-delay: 0s, 0s, 0.125s;
}
#editor-toolbar .-help h3 {
font-size: 1.25em;
margin-bottom: 0.25rem;
border-bottom: 1px solid currentColor;
}
#editor-toolbar button:hover .-help {
opacity: 1;
z-index: 2; /* show above any that are in mid-fade */
visibility: visible;
margin-top: 0.25em;
transition-delay: 0.5s;
transition-timing-function: ease-in;
}
#editor .controls .-buttons {
grid-area: menu;
}
#editor .controls #editor-tool-help {
grid-area: hint;
}
.icon-button-set {
display: flex;
flex-wrap: wrap;
@ -988,6 +1030,7 @@ main.--has-demo .demo-controls {
}
#editor .palette {
isolation: isolate;
grid-area: palette;
padding-right: 0.25em; /* in case of scrollbar */
overflow-y: auto;
@ -1073,3 +1116,12 @@ ol.editor-letter-tile-picker input[type=radio]:checked + .-glyph {
background: hsl(225, 75%, 90%);
outline: 2px solid hsl(225, 75%, 80%);
}
/* Hint tiles accept prose */
textarea.editor-hint-tile-text {
font-size: 1.5em;
width: 20vw;
height: 20vh;
min-width: 15rem;
min-height: 5rem;
font-family: serif;
}