Workers P2: Sichtprüfung im Browser – Schilder über den Figuren, Raumtitel über den Möbeln, Zoomknöpfe unten, Abstand der Meldung
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d67f8cc471
commit
820ccd510d
9 files changed
+122
-21
No files matched your search
@@ -13,6 +13,7 @@ const GREY_TINT = '#9aa3b2'
|
|||||||
const DIVIDER = '#e1e5ec'
|
const DIVIDER = '#e1e5ec'
|
||||||
const SIGN_TEXT = 'noch nicht angeschlossen'
|
const SIGN_TEXT = 'noch nicht angeschlossen'
|
||||||
const SIGN_MAX_W = 60
|
const SIGN_MAX_W = 60
|
||||||
|
const TITLE_HALO = 'rgba(255, 255, 255, 0.85)' // Rand der Raumtitel
|
||||||
const STEP_COLORS = [TEAM.radar, TEAM.bewertung, TEAM.werkstatt, TEAM.pruefung, TEAM.freigabe] // Stationen der Pipeline-Tafel
|
const STEP_COLORS = [TEAM.radar, TEAM.bewertung, TEAM.werkstatt, TEAM.pruefung, TEAM.freigabe] // Stationen der Pipeline-Tafel
|
||||||
const FEED_ROWS = [8, 5, 9] // Breiten der durchlaufenden Zeilen im Feed-Schirm
|
const FEED_ROWS = [8, 5, 9] // Breiten der durchlaufenden Zeilen im Feed-Schirm
|
||||||
const MAX_PAGE_DOTS = 19
|
const MAX_PAGE_DOTS = 19
|
||||||
@@ -274,9 +275,16 @@ export const ROOM_DRAWERS = Object.freeze({
|
|||||||
|
|
||||||
// ---------- Zeichnen ----------
|
// ---------- Zeichnen ----------
|
||||||
|
|
||||||
function drawRoom(ctx, room, floor, index, t, look) {
|
const isGrey = (room, look) => room.workerId != null && look.greyRooms.has(room.id)
|
||||||
|
|
||||||
|
function drawRoomSign(ctx, room, floor) {
|
||||||
|
const w = Math.min(room.w - 2, SIGN_MAX_W)
|
||||||
|
drawSign(ctx, room.x + (room.w - w) / 2, floor.top + 1, w, SIGN_TEXT)
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawRoom(ctx, room, floor, index, t, look, signs) {
|
||||||
// Räume ohne Worker (pult, tresor) werden nie grau
|
// Räume ohne Worker (pult, tresor) werden nie grau
|
||||||
const grey = room.workerId != null && look.greyRooms.has(room.id)
|
const grey = isGrey(room, look)
|
||||||
const r = {
|
const r = {
|
||||||
room,
|
room,
|
||||||
x: room.x,
|
x: room.x,
|
||||||
@@ -292,19 +300,30 @@ function drawRoom(ctx, room, floor, index, t, look) {
|
|||||||
// Teamfarbton hat jeder Raum, auch ein grauer (die Grautönung kommt darüber)
|
// Teamfarbton hat jeder Raum, auch ein grauer (die Grautönung kommt darüber)
|
||||||
tint(ctx, room.x, floor.top, room.w, floor.h, r.color, TINT_ALPHA)
|
tint(ctx, room.x, floor.top, room.w, floor.h, r.color, TINT_ALPHA)
|
||||||
if (index > 0) px(ctx, room.x, floor.top, 1, floor.h, DIVIDER)
|
if (index > 0) px(ctx, room.x, floor.top, 1, floor.h, DIVIDER)
|
||||||
// Der Titel steht in der obersten Zeile des Raums. Im grauen Raum nimmt das Schild (44 breit, 8 hoch) diese Zeile ein:
|
|
||||||
// über dem Hut der schlafenden Figur sind nur 6 Zeilen frei, Titel (3,3) und Schild (8) passen in den 46 breiten
|
|
||||||
// Such-Räumen, den einzigen, die grau sein können, nicht übereinander. Name und Branche zeigen dort Möbel und Icon.
|
|
||||||
if (!grey) label(ctx, room.title, room.x + 2, floor.top + 1, { size: 10, color: PAL.mute, weight: 500 })
|
|
||||||
if (Object.hasOwn(ROOM_DRAWERS, room.kind)) ROOM_DRAWERS[room.kind](ctx, r)
|
if (Object.hasOwn(ROOM_DRAWERS, room.kind)) ROOM_DRAWERS[room.kind](ctx, r)
|
||||||
|
// Der Titel steht in der obersten Zeile des Raums, über den Möbeln (ein Monitor deckt sonst den Anfang zu) und mit hellem
|
||||||
|
// Rand, damit er auch über einem dunklen Bildschirm lesbar bleibt. Im grauen Raum nimmt das Schild (44 breit, 8 hoch) diese
|
||||||
|
// Zeile ein: über dem Hut der schlafenden Figur sind nur 6 Zeilen frei, Titel (3,3) und Schild (8) passen in den 46 breiten
|
||||||
|
// Such-Räumen, den einzigen, die grau sein können, nicht übereinander. Name und Branche zeigen dort Möbel und Icon.
|
||||||
|
if (!grey) label(ctx, room.title, room.x + 2, floor.top + 1, { size: 10, color: PAL.mute, weight: 500, halo: TITLE_HALO })
|
||||||
if (grey) {
|
if (grey) {
|
||||||
tint(ctx, room.x, floor.top, room.w, floor.h, GREY_TINT, GREY_ALPHA)
|
tint(ctx, room.x, floor.top, room.w, floor.h, GREY_TINT, GREY_ALPHA)
|
||||||
const w = Math.min(room.w - 2, SIGN_MAX_W)
|
if (signs) drawRoomSign(ctx, room, floor)
|
||||||
drawSign(ctx, room.x + (room.w - w) / 2, floor.top + 1, w, SIGN_TEXT)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alle Räume aller Etagen. Vorher: drawOutside, drawTower. Nachher: Aufzug hinten, Figuren, Aufzug vorn.
|
// Alle Räume aller Etagen. Vorher: drawOutside, drawTower. Nachher: Aufzug hinten, Figuren, Aufzug vorn.
|
||||||
export function drawRooms(ctx, layout, t, look) {
|
// signs = false lässt die Schilder der grauen Räume aus: die Szene zeichnet sie mit drawSigns erst nach den Figuren,
|
||||||
for (const floor of layout.floors) floor.rooms.forEach((room, index) => drawRoom(ctx, room, floor, index, t, look))
|
// sonst deckt der Hut der schlafenden Figur die untere Zeile des Schildtexts zu.
|
||||||
|
export function drawRooms(ctx, layout, t, look, { signs = true } = {}) {
|
||||||
|
for (const floor of layout.floors) floor.rooms.forEach((room, index) => drawRoom(ctx, room, floor, index, t, look, signs))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Die Schilder „noch nicht angeschlossen“ der grauen Räume (Gegenstück zu drawRooms mit signs: false)
|
||||||
|
export function drawSigns(ctx, layout, look) {
|
||||||
|
for (const floor of layout.floors) {
|
||||||
|
for (const room of floor.rooms) {
|
||||||
|
if (isGrey(room, look)) drawRoomSign(ctx, room, floor)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ import { WORLD, buildLayout } from './layout.js'
|
|||||||
import { createCamera, fitZoom, viewToWorld, worldToView, zoomAt, panBy, centerOn, pickAgent } from './camera.js'
|
import { createCamera, fitZoom, viewToWorld, worldToView, zoomAt, panBy, centerOn, pickAgent } from './camera.js'
|
||||||
import { createLift } from './lift.js'
|
import { createLift } from './lift.js'
|
||||||
import { createFx } from './fx.js'
|
import { createFx } from './fx.js'
|
||||||
import { createLook, drawRooms } from './rooms.js'
|
import { createLook, drawRooms, drawSigns } from './rooms.js'
|
||||||
import { drawOutside, drawTower, drawPauseVeil } from './building.js'
|
import { drawOutside, drawTower, drawPauseVeil } from './building.js'
|
||||||
import { createDirector, drawAgents } from './director.js'
|
import { createDirector, drawAgents } from './director.js'
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ function inertHandle() {
|
|||||||
→ { destroy(), setState(state), pushEvents(events), select(workerId|null), setPaused(bool), flush(), resize(), zoomBy(faktor),
|
→ { destroy(), setState(state), pushEvents(events), select(workerId|null), setPaused(bool), flush(), resize(), zoomBy(faktor),
|
||||||
resetView(), locate(workerId): { x, y } | null, stats(): { frames, avgDrawMs, zoom }, debug() }
|
resetView(), locate(workerId): { x, y } | null, stats(): { frames, avgDrawMs, zoom }, debug() }
|
||||||
Rasterweite je Bild: k = Canvas-Breite / (320 / zoom). Reihenfolge je Bild: Weiß, Außenwelt, Turm, Räume, Aufzug hinten, Figuren,
|
Rasterweite je Bild: k = Canvas-Breite / (320 / zoom). Reihenfolge je Bild: Weiß, Außenwelt, Turm, Räume, Aufzug hinten, Figuren,
|
||||||
Aufzug vorn, Effekte, Pausenschleier. onSelect(id | null) kommt nur bei Klicks des Nutzers, nie bei select().
|
Schilder der grauen Räume, Aufzug vorn, Effekte, Pausenschleier. onSelect(id | null) kommt nur bei Klicks des Nutzers, nie bei select().
|
||||||
reducedMotion (und fehlendes raf): keine Schleife; setState, pushEvents, select, resize und die Kamera zeichnen je ein Standbild,
|
reducedMotion (und fehlendes raf): keine Schleife; setState, pushEvents, select, resize und die Kamera zeichnen je ein Standbild,
|
||||||
pushEvents spielt die Regie sofort zu Ende. fontsReady (Promise): nach dem Laden der Schrift wird einmal neu gezeichnet.
|
pushEvents spielt die Regie sofort zu Ende. fontsReady (Promise): nach dem Laden der Schrift wird einmal neu gezeichnet.
|
||||||
flush() beruhigt die Regie auf einen Schlag (alles zu Ende gespielt, alle Figuren heim, Aufzug und Effekte leer): die Bühne ruft es
|
flush() beruhigt die Regie auf einen Schlag (alles zu Ende gespielt, alle Figuren heim, Aufzug und Effekte leer): die Bühne ruft es
|
||||||
@@ -98,9 +98,10 @@ export function mountTower(canvas, options = {}) {
|
|||||||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||||
drawOutside(ctx)
|
drawOutside(ctx)
|
||||||
drawTower(ctx, layout, time, lift)
|
drawTower(ctx, layout, time, lift)
|
||||||
drawRooms(ctx, layout, time, look)
|
drawRooms(ctx, layout, time, look, { signs: false })
|
||||||
lift.drawBack(ctx)
|
lift.drawBack(ctx)
|
||||||
drawAgents(ctx, director, selectedId, time)
|
drawAgents(ctx, director, selectedId, time)
|
||||||
|
drawSigns(ctx, layout, look)
|
||||||
lift.drawFront(ctx)
|
lift.drawFront(ctx)
|
||||||
fx.draw(ctx)
|
fx.draw(ctx)
|
||||||
drawPauseVeil(ctx, look)
|
drawPauseVeil(ctx, look)
|
||||||
|
|||||||
@@ -227,8 +227,9 @@ export function drawAlertMark(ctx, x, y) {
|
|||||||
const fontOf = (weight, devPx) => `${weight} ${Math.round(devPx * 100) / 100}px ${FONT_FAMILY}`
|
const fontOf = (weight, devPx) => `${weight} ${Math.round(devPx * 100) / 100}px ${FONT_FAMILY}`
|
||||||
|
|
||||||
/* Text an (x, y) in logischen Einheiten (y = Oberkante der Zeile). Schrift = size * k / 3 Geräte-Pixel,
|
/* Text an (x, y) in logischen Einheiten (y = Oberkante der Zeile). Schrift = size * k / 3 Geräte-Pixel,
|
||||||
bei k = 3 also size px. bg: Farbe eines Kastens hinter dem Text; align: 'left' | 'center' | 'right'. */
|
bei k = 3 also size px. bg: Farbe eines Kastens hinter dem Text; halo: Farbe eines dünnen Rands um die Buchstaben
|
||||||
export function label(ctx, text, x, y, { size = 12, color = PAL.ink, weight = 500, align = 'left', bg } = {}) {
|
(lesbar auch über dunklen Flächen wie einem Monitor); align: 'left' | 'center' | 'right'. */
|
||||||
|
export function label(ctx, text, x, y, { size = 12, color = PAL.ink, weight = 500, align = 'left', bg, halo } = {}) {
|
||||||
const { k, ox, oy } = gridOf(ctx)
|
const { k, ox, oy } = gridOf(ctx)
|
||||||
const f = k / 3
|
const f = k / 3
|
||||||
const str = String(text ?? '')
|
const str = String(text ?? '')
|
||||||
@@ -243,6 +244,12 @@ export function label(ctx, text, x, y, { size = 12, color = PAL.ink, weight = 50
|
|||||||
ctx.fillStyle = bg
|
ctx.fillStyle = bg
|
||||||
ctx.fillRect(Math.round(left - 4 * f), Math.round(dy - 2 * f), Math.round(tw + 8 * f), Math.round(size * f + 5 * f))
|
ctx.fillRect(Math.round(left - 4 * f), Math.round(dy - 2 * f), Math.round(tw + 8 * f), Math.round(size * f + 5 * f))
|
||||||
}
|
}
|
||||||
|
if (halo) {
|
||||||
|
ctx.lineJoin = 'round'
|
||||||
|
ctx.lineWidth = Math.max(2, Math.round(3 * f))
|
||||||
|
ctx.strokeStyle = halo
|
||||||
|
ctx.strokeText(str, dx, dy)
|
||||||
|
}
|
||||||
ctx.fillStyle = color
|
ctx.fillStyle = color
|
||||||
ctx.fillText(str, dx, dy)
|
ctx.fillText(str, dx, dy)
|
||||||
ctx.textAlign = 'left'
|
ctx.textAlign = 'left'
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
.wk-status { color: var(--text-2); }
|
.wk-status { color: var(--text-2); }
|
||||||
|
/* Meldungen zwischen Kopfzeile und Kennzahlen (Notbremse, Fehler) halten Abstand zu den Kennzahlen */
|
||||||
|
.wk-toolbar ~ .msg { margin-bottom: 16px; }
|
||||||
|
|
||||||
/* ---------- Kennzahlen ---------- */
|
/* ---------- Kennzahlen ---------- */
|
||||||
|
|
||||||
@@ -59,9 +61,10 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
touch-action: pan-y;
|
touch-action: pan-y;
|
||||||
}
|
}
|
||||||
|
/* unten rechts: oben rechts läge er über dem Schild der Kommandobrücke */
|
||||||
.wk-zoom {
|
.wk-zoom {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
bottom: 8px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|||||||
@@ -2,15 +2,18 @@
|
|||||||
// fillRect/fillText werden mit dem aktuellen fillStyle/font notiert, alles Übrige ist ein No-op.
|
// fillRect/fillText werden mit dem aktuellen fillStyle/font notiert, alles Übrige ist ein No-op.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* makeCtx({ width, height }) → { ctx, rects, texts, counts, alphas }
|
* makeCtx({ width, height }) → { ctx, rects, texts, strokes, counts, alphas }
|
||||||
* rects: [{ x, y, w, h, fill }] jedes fillRect
|
* rects: [{ x, y, w, h, fill }] jedes fillRect
|
||||||
* texts: [{ text, x, y, font }] jedes fillText (Nicht-String wirft wie im Smoke-Test)
|
* texts: [{ text, x, y, font }] jedes fillText (Nicht-String wirft wie im Smoke-Test)
|
||||||
|
* strokes: [{ text, x, y, style, width, rects, texts }] jedes strokeText (style/width = strokeStyle/lineWidth, rects/texts =
|
||||||
|
* Anzahl der bis dahin gezeichneten fillRect/fillText: so ist die Reihenfolge prüfbar)
|
||||||
* counts: { save, restore } Aufrufe von ctx.save()/ctx.restore()
|
* counts: { save, restore } Aufrufe von ctx.save()/ctx.restore()
|
||||||
* alphas: [] jede Zuweisung an ctx.globalAlpha
|
* alphas: [] jede Zuweisung an ctx.globalAlpha
|
||||||
*/
|
*/
|
||||||
export function makeCtx({ width = 960, height = 768 } = {}) {
|
export function makeCtx({ width = 960, height = 768 } = {}) {
|
||||||
const rects = []
|
const rects = []
|
||||||
const texts = []
|
const texts = []
|
||||||
|
const strokes = []
|
||||||
const counts = { save: 0, restore: 0 }
|
const counts = { save: 0, restore: 0 }
|
||||||
const alphas = []
|
const alphas = []
|
||||||
const target = { canvas: { width, height }, fillStyle: '#000000', font: '10px sans-serif', globalAlpha: 1 }
|
const target = { canvas: { width, height }, fillStyle: '#000000', font: '10px sans-serif', globalAlpha: 1 }
|
||||||
@@ -27,6 +30,12 @@ export function makeCtx({ width = 960, height = 768 } = {}) {
|
|||||||
texts.push({ text, x, y, font: t.font })
|
texts.push({ text, x, y, font: t.font })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (prop === 'strokeText') {
|
||||||
|
return (text, x, y) => {
|
||||||
|
if (typeof text !== 'string') throw new Error('strokeText mit Nicht-String: ' + text)
|
||||||
|
strokes.push({ text, x, y, style: t.strokeStyle, width: t.lineWidth, rects: rects.length, texts: texts.length })
|
||||||
|
}
|
||||||
|
}
|
||||||
if (prop === 'createLinearGradient' || prop === 'createRadialGradient') return () => ({ addColorStop() {} })
|
if (prop === 'createLinearGradient' || prop === 'createRadialGradient') return () => ({ addColorStop() {} })
|
||||||
if (prop === 'getImageData') return () => ({ data: new Uint8ClampedArray(4) })
|
if (prop === 'getImageData') return () => ({ data: new Uint8ClampedArray(4) })
|
||||||
if (prop in t) return t[prop]
|
if (prop in t) return t[prop]
|
||||||
@@ -38,5 +47,5 @@ export function makeCtx({ width = 960, height = 768 } = {}) {
|
|||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return { ctx, rects, texts, counts, alphas }
|
return { ctx, rects, texts, strokes, counts, alphas }
|
||||||
}
|
}
|
||||||
@@ -12,3 +12,9 @@ test('jede im JSX verwendete wk-Klasse ist definiert', () => {
|
|||||||
const css = readFileSync(new URL('../../src/styles/workers.css', import.meta.url), 'utf8'); const dir = new URL('../../src/components/workers/', import.meta.url)
|
const css = readFileSync(new URL('../../src/styles/workers.css', import.meta.url), 'utf8'); const dir = new URL('../../src/components/workers/', import.meta.url)
|
||||||
for (const f of readdirSync(dir).filter((x) => x.endsWith('.jsx'))) for (const m of readFileSync(new URL(f, dir), 'utf8').matchAll(/(?<![\w-])wk-[a-z0-9-]+/g)) assert.ok(css.includes('.' + m[0]), `${f}: ${m[0]}`) // Lookbehind: das Attribut data-wk-worker ist keine Klasse
|
for (const f of readdirSync(dir).filter((x) => x.endsWith('.jsx'))) for (const m of readFileSync(new URL(f, dir), 'utf8').matchAll(/(?<![\w-])wk-[a-z0-9-]+/g)) assert.ok(css.includes('.' + m[0]), `${f}: ${m[0]}`) // Lookbehind: das Attribut data-wk-worker ist keine Klasse
|
||||||
})
|
})
|
||||||
|
test('workers.css: Zoomknöpfe unten rechts (oben läge die Reihe über dem Schild der Kommandobrücke), Meldungen halten Abstand zu den Kennzahlen', () => {
|
||||||
|
const css = readFileSync(new URL('../../src/styles/workers.css', import.meta.url), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '')
|
||||||
|
const rule = (selector) => css.match(new RegExp(`${selector.replace(/[.~]/g, '\\$&')}\\s*\\{([^}]*)\\}`))?.[1] ?? ''
|
||||||
|
assert.match(rule('.wk-zoom'), /bottom:\s*8px/); assert.match(rule('.wk-zoom'), /right:\s*8px/); assert.doesNotMatch(rule('.wk-zoom'), /\btop:/)
|
||||||
|
assert.match(rule('.wk-toolbar ~ .msg'), /margin-bottom:\s*16px/)
|
||||||
|
})
|
||||||
@@ -297,3 +297,40 @@ test('grauer Raum: Teamfarbton bleibt, das Schild ersetzt den Titel (kein Platz
|
|||||||
assert.equal(grey.texts.filter((x) => x.text === SIGN).length, 1)
|
assert.equal(grey.texts.filter((x) => x.text === SIGN).length, 1)
|
||||||
assert.equal(normal.texts.filter((x) => x.text === SIGN).length, 0)
|
assert.equal(normal.texts.filter((x) => x.text === SIGN).length, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Schilder: drawRooms mit signs: false lässt sie aus (Grautönung bleibt), drawSigns zeichnet genau diese Schilder an dieselbe Stelle', () => {
|
||||||
|
const look = looks({ greyRooms: new Set(['kleinanzeigen', 'portale']) })
|
||||||
|
const withSigns = draw(look)
|
||||||
|
const m = makeCtx()
|
||||||
|
R.drawRooms(m.ctx, layout, 1, look, { signs: false })
|
||||||
|
assert.equal(m.texts.filter((x) => x.text === SIGN).length, 0)
|
||||||
|
assert.equal(m.rects.length < withSigns.rects.length, true)
|
||||||
|
assert.ok(m.alphas.includes(0.45), 'die Grautönung gehört weiter zum Raum')
|
||||||
|
assert.deepEqual(m.texts.filter((x) => x.text !== SIGN).map((x) => x.text), withSigns.texts.filter((x) => x.text !== SIGN).map((x) => x.text))
|
||||||
|
const s = makeCtx()
|
||||||
|
R.drawSigns(s.ctx, layout, look)
|
||||||
|
assert.deepEqual(s.texts.map((x) => x.text), [SIGN, SIGN])
|
||||||
|
assert.deepEqual(s.texts.map((x) => [x.x, x.y]), withSigns.texts.filter((x) => x.text === SIGN).map((x) => [x.x, x.y]))
|
||||||
|
const none = makeCtx()
|
||||||
|
R.drawSigns(none.ctx, layout, R.createLook())
|
||||||
|
assert.equal(none.rects.length + none.texts.length, 0)
|
||||||
|
const noWorker = makeCtx()
|
||||||
|
R.drawSigns(noWorker.ctx, layout, looks({ greyRooms: new Set(['pult', 'tresor']) })) // Räume ohne Worker werden nie grau
|
||||||
|
assert.equal(noWorker.texts.length, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Raumtitel: über den Möbeln gezeichnet (ein Monitor deckt sie nicht zu) und mit hellem Rand', () => {
|
||||||
|
const floor = layout.floors.find((f) => f.id === 'gespraeche')
|
||||||
|
const room = floor.rooms.find((r) => r.id === 'mail')
|
||||||
|
const m = makeCtx()
|
||||||
|
R.drawRooms(m.ctx, { ...layout, floors: [{ ...floor, rooms: [room] }] }, 1, R.createLook())
|
||||||
|
assert.equal(m.strokes.length, 1)
|
||||||
|
assert.equal(m.strokes[0].text, room.title)
|
||||||
|
assert.ok(m.strokes[0].style.startsWith('rgba(255, 255, 255'))
|
||||||
|
assert.equal(m.strokes[0].rects, m.rects.length, 'nach dem Titel kommt keine Fläche mehr: Möbel, Tisch und Monitor liegen darunter')
|
||||||
|
assert.ok(m.rects.length > 20)
|
||||||
|
assert.equal(m.texts.filter((x) => x.text === room.title).length, 1)
|
||||||
|
const grey = makeCtx()
|
||||||
|
R.drawRooms(grey.ctx, { ...layout, floors: [{ ...floor, rooms: [{ ...room, id: 'kleinanzeigen' }] }] }, 1, looks({ greyRooms: new Set(['kleinanzeigen']) }))
|
||||||
|
assert.equal(grey.strokes.length, 0, 'im grauen Raum steht kein Titel')
|
||||||
|
})
|
||||||
@@ -212,7 +212,7 @@ test('resize() zieht den Startausschnitt nach, bis der Nutzer die Kamera bewegt
|
|||||||
|
|
||||||
// ---------- Zeichnen ----------
|
// ---------- Zeichnen ----------
|
||||||
|
|
||||||
test('Zeichenreihenfolge: Weiß, Außenwelt, Turm, Räume, Aufzug hinten, Figuren, Aufzug vorn, Pausenschleier', async () => {
|
test('Zeichenreihenfolge: Weiß, Außenwelt, Turm, Räume, Aufzug hinten, Figuren, Schilder, Aufzug vorn, Pausenschleier', async () => {
|
||||||
const { sb, h } = await open({ record: true })
|
const { sb, h } = await open({ record: true })
|
||||||
const state = makeState({ paused: true, connected: { kim: false } })
|
const state = makeState({ paused: true, connected: { kim: false } })
|
||||||
h.setState(state)
|
h.setState(state)
|
||||||
@@ -233,12 +233,14 @@ test('Zeichenreihenfolge: Weiß, Außenwelt, Turm, Räume, Aufzug hinten, Figure
|
|||||||
}
|
}
|
||||||
const outside = rectN((r) => r.fill === '#f7f4f0', 'Erde der Außenwelt')
|
const outside = rectN((r) => r.fill === '#f7f4f0', 'Erde der Außenwelt')
|
||||||
const tower = rectN((r) => r.fill === '#e4e8ef', 'Aufzugschacht')
|
const tower = rectN((r) => r.fill === '#e4e8ef', 'Aufzugschacht')
|
||||||
const rooms = textN('noch nicht angeschlossen')
|
const rooms = textN('Netz-Radar') // Raumtitel
|
||||||
const liftBack = rectN((r) => r.fill === '#f1f3f8', 'Rückwand der Kabine')
|
const liftBack = rectN((r) => r.fill === '#f1f3f8', 'Rückwand der Kabine')
|
||||||
const figure = rectN((r) => r.alpha === 0.55, 'graue Figur')
|
const figure = rectN((r) => r.alpha === 0.55, 'graue Figur')
|
||||||
|
const sign = textN('noch nicht angeschlossen') // das Schild liegt über dem Hut der schlafenden Figur (sonst deckt der Hut den Text zu)
|
||||||
const liftFront = rectN((r) => r.fill === '#ffe9a8', 'Lampe der Kabine')
|
const liftFront = rectN((r) => r.fill === '#ffe9a8', 'Lampe der Kabine')
|
||||||
const veil = textN('Pausiert')
|
const veil = textN('Pausiert')
|
||||||
assert.deepEqual([outside, tower, rooms, liftBack, figure, liftFront, veil].toSorted((a, b) => a - b), [outside, tower, rooms, liftBack, figure, liftFront, veil])
|
assert.deepEqual([outside, tower, rooms, liftBack, figure, sign, liftFront, veil].toSorted((a, b) => a - b), [outside, tower, rooms, liftBack, figure, sign, liftFront, veil])
|
||||||
|
assert.equal(sb.texts.filter((t) => t.text === 'noch nicht angeschlossen').length, 1, 'genau ein Schild, nicht zweimal')
|
||||||
assert.equal(sb.texts.at(-1).text, 'Pausiert')
|
assert.equal(sb.texts.at(-1).text, 'Pausiert')
|
||||||
h.destroy()
|
h.destroy()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -63,6 +63,23 @@ test('label: Zahl wird zu Text, Hintergrund folgt der Ausrichtung', () => {
|
|||||||
assert.deepEqual([c.rects[0].x, c.rects[0].y, c.rects[0].w, c.rects[0].h, c.rects[0].fill], [12, 13, 36, 17, '#ffffff'])
|
assert.deepEqual([c.rects[0].x, c.rects[0].y, c.rects[0].w, c.rects[0].h, c.rects[0].fill], [12, 13, 36, 17, '#ffffff'])
|
||||||
assert.equal(c.texts[0].x, 30)
|
assert.equal(c.texts[0].x, 30)
|
||||||
})
|
})
|
||||||
|
test('label: halo zeichnet erst einen hellen Rand (strokeText), dann den Text darüber; ohne halo kein Rand', () => {
|
||||||
|
const h = makeCtx()
|
||||||
|
S.setGrid(h.ctx, { k: 3 })
|
||||||
|
S.label(h.ctx, 'Mail-Stube', 2, 1, { size: 10, color: '#445566', halo: '#ffffff' })
|
||||||
|
assert.equal(h.strokes.length, 1)
|
||||||
|
assert.deepEqual([h.strokes[0].text, h.strokes[0].style], ['Mail-Stube', '#ffffff'])
|
||||||
|
assert.deepEqual([h.strokes[0].x, h.strokes[0].y], [h.texts[0].x, h.texts[0].y], 'Rand und Text an derselben Stelle')
|
||||||
|
assert.equal(h.strokes[0].texts, 0, 'der Rand kommt vor dem Text')
|
||||||
|
assert.ok(h.strokes[0].width >= 2, 'mindestens ein Geräte-Pixel Rand rundum')
|
||||||
|
const big = makeCtx()
|
||||||
|
S.setGrid(big.ctx, { k: 6 })
|
||||||
|
S.label(big.ctx, 'Mail-Stube', 2, 1, { size: 10, halo: '#ffffff' })
|
||||||
|
assert.ok(big.strokes[0].width > h.strokes[0].width, 'der Rand wächst mit dem Raster')
|
||||||
|
const plain = makeCtx()
|
||||||
|
S.label(plain.ctx, 'Mail-Stube', 2, 1, { size: 10 })
|
||||||
|
assert.equal(plain.strokes.length, 0)
|
||||||
|
})
|
||||||
test('drawWorker: Schlafen zeigt ein z über dem Hut, das mit t steigt; stehen nicht', () => {
|
test('drawWorker: Schlafen zeigt ein z über dem Hut, das mit t steigt; stehen nicht', () => {
|
||||||
const zs = (pose, t) => {
|
const zs = (pose, t) => {
|
||||||
const { ctx, rects } = makeCtx()
|
const { ctx, rects } = makeCtx()
|
||||||
|
|||||||
Reference in new issue
Block a user