minitel-standalone
Advanced tools
Comparing version 1.10.2 to 2.0.0
@@ -7,2 +7,3 @@ /// <reference types="node" resolution-mode="require"/> | ||
import { Focusable } from './focusable.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
export declare class MinitelObject<T extends MinitelObjectAttributes = MinitelObjectAttributes, U extends Record<string, any[]> = Record<string, any[]>> extends EventEmitter<U> { | ||
@@ -34,2 +35,8 @@ children: MinitelObject[]; | ||
has(child: MinitelObject): boolean; | ||
mapLocation(attributes: T, inheritMe: Partial<T>, nextNode: MinitelObject, nodes: MinitelObject[], weAt: number): LocationDescriptor; | ||
mapLocationWrapper(inheritedAttributes: Partial<T>, forcedAttributes: Partial<T>, nodes: MinitelObject[], weAt: number): LocationDescriptor; | ||
get parentList(): MinitelObject[]; | ||
scrollIntoView(context?: MinitelObject & { | ||
scrollDelta: [number, number]; | ||
}): void; | ||
} |
@@ -79,9 +79,11 @@ import { EventEmitter } from 'node:events'; | ||
if (attributes.width != null) | ||
result.setWidth(attributes.width, 'end'); | ||
result.setWidth(attributes.width, 'end', fillChar); | ||
if (attributes.height != null) | ||
result.setHeight(attributes.height, 'end'); | ||
result.setHeight(attributes.height, 'end', fillChar); | ||
result.pad(pad, fillChar); | ||
// Descriptor before pad, is this the right choice? | ||
// Future you: Yes. Yes it is. | ||
// Future you 5 minutes later: Actually you know what | ||
if (this.keepElmDesc) | ||
result.locationDescriptors.add(this, new LocationDescriptor(0, 0, result.width, result.height)); | ||
result.pad(pad, fillChar); | ||
return result; | ||
@@ -107,2 +109,54 @@ } | ||
} | ||
mapLocation(attributes, inheritMe, nextNode, nodes, weAt) { | ||
return nextNode.mapLocationWrapper(inheritMe, {}, nodes, weAt); | ||
} | ||
mapLocationWrapper(inheritedAttributes, forcedAttributes, nodes, weAt) { | ||
const nextNode = nodes[weAt + 1]; | ||
const nextNodeIdx = nextNode && this.children.indexOf(nextNode); | ||
if (nextNodeIdx === -1) { | ||
throw new Error(`Next node was not found in children. This behaviour is unexpected; please contact the owner of minitel-standalone.`); | ||
} | ||
const attributes = Object.assign(Object.assign(Object.assign(Object.assign({}, this.defaultAttributes), inheritedAttributes), this.attributes), forcedAttributes); | ||
const pad = padding.normalise(attributes.pad); | ||
attributes.width = attributes.width != null ? padding.exludeX(attributes.width, pad) : null; | ||
attributes.height = attributes.height != null ? padding.exludeY(attributes.height, pad) : null; | ||
if (!nextNode) { | ||
const dimensions = this.getDimensionsWrapper(attributes, inheritedProps(Object.assign(Object.assign(Object.assign({}, inheritedAttributes), this.attributes), forcedAttributes))); | ||
return new LocationDescriptor(0, 0, dimensions.width, dimensions.height); | ||
} | ||
let result = this.mapLocation(attributes, inheritedProps(Object.assign(Object.assign(Object.assign({}, inheritedAttributes), this.attributes), forcedAttributes)), nextNode, nodes, weAt + 1); | ||
result.x += pad[0]; | ||
result.y += pad[3]; | ||
return result; | ||
} | ||
get parentList() { | ||
var _a; | ||
return [...(((_a = this.parent) === null || _a === void 0 ? void 0 : _a.parentList) || []), this]; | ||
} | ||
scrollIntoView(context) { | ||
if (!context) { | ||
const parentList = this.parentList.filter((v) => 'scrollDelta' in v); | ||
for (let i = 0; i < parentList.length - 1; i += 1) | ||
parentList[i + 1].scrollIntoView(parentList[i]); | ||
this.scrollIntoView(parentList.at(-1)); | ||
return; | ||
} | ||
const pathToThis = this.parentList; | ||
const pathToScrollable = context.parentList; | ||
const thisPos = this.minitel.mapLocationWrapper({}, {}, pathToThis, 0); | ||
const scrollablePos = this.minitel.mapLocationWrapper({}, {}, pathToScrollable, 0); | ||
const [relY, relX] = [thisPos.y - scrollablePos.y, thisPos.x - scrollablePos.x]; | ||
if (relY < 0) { | ||
context.scrollDelta[0] += relY; | ||
} | ||
else if (relY + thisPos.h > scrollablePos.h) { | ||
context.scrollDelta[0] -= scrollablePos.h - (relY + thisPos.h); | ||
} | ||
if (relX < 0) { | ||
context.scrollDelta[1] += relX; | ||
} | ||
else if (relX + thisPos.w > scrollablePos.w) { | ||
context.scrollDelta[1] -= scrollablePos.w - (relX + thisPos.w); | ||
} | ||
} | ||
} | ||
@@ -109,0 +163,0 @@ MinitelObject.defaultAttributes = { |
@@ -5,2 +5,3 @@ import { MinitelObject } from './minitelobject.js'; | ||
import type { Minitel } from '../index.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
export declare class TextNode extends MinitelObject { | ||
@@ -15,2 +16,3 @@ text: string; | ||
renderLines(inheritedAttributes: Partial<MinitelObjectAttributes>, forcedAttributes: Partial<RenderLinesAttributes>): RichCharGrid[]; | ||
mapLocation(attributes: MinitelObjectAttributes, inheritMe: Partial<MinitelObjectAttributes>, nextNode: MinitelObject): LocationDescriptor; | ||
} |
@@ -87,2 +87,5 @@ import { MinitelObject } from './minitelobject.js'; | ||
} | ||
mapLocation(attributes, inheritMe, nextNode) { | ||
throw new Error('TextNode doesn\'t have children, and therefore can\'t mapLocation'); | ||
} | ||
} |
@@ -7,3 +7,3 @@ import { MinitelObject } from '../abstract/minitelobject.js'; | ||
defaultAttributes: T; | ||
constructor(children: never[] | undefined, attributes: Partial<T>, minitel: Minitel); | ||
constructor(children: MinitelObject<MinitelObjectAttributes, Record<string, any[]>>[] | undefined, attributes: Partial<T>, minitel: Minitel); | ||
getDimensions(attributes: T, inheritMe: Partial<T>): { | ||
@@ -10,0 +10,0 @@ width: number; |
import { FocusableAttributes as FocusableIfaceAttributes, Focusable as FocusableIface } from '../abstract/focusable.js'; | ||
import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { Container, ContainerAttributes } from './container.js'; | ||
@@ -13,7 +14,6 @@ import type { Minitel } from './minitel.js'; | ||
keepElmDesc: true; | ||
private artificialBlink; | ||
set focused(val: boolean); | ||
get focused(): boolean; | ||
constructor(children: never[] | undefined, attributes: Partial<FocusableAttributes>, minitel: Minitel); | ||
constructor(children: MinitelObject<import("../types.js").MinitelObjectAttributes, Record<string, any[]>>[] | undefined, attributes: Partial<FocusableAttributes>, minitel: Minitel); | ||
get disabled(): boolean; | ||
} |
@@ -8,10 +8,13 @@ import { Container } from './container.js'; | ||
if (val) { | ||
if (this.minitel.focusedObj) | ||
// console.log(this.minitel.focusedObj, this.minitel.focusedObj === this); | ||
if (this.minitel.focusedObj && this.minitel.focusedObj !== this) | ||
this.minitel.focusedObj.focused = false; | ||
if (this._focused !== val) | ||
(_b = (_a = this.attributes).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a); | ||
this._focused = true; | ||
(_b = (_a = this.attributes).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a); | ||
} | ||
else { | ||
if (this._focused !== val) | ||
(_d = (_c = this.attributes).onBlur) === null || _d === void 0 ? void 0 : _d.call(_c); | ||
this._focused = false; | ||
(_d = (_c = this.attributes).onBlur) === null || _d === void 0 ? void 0 : _d.call(_c); | ||
} | ||
@@ -27,3 +30,2 @@ } | ||
this.keepElmDesc = true; | ||
this.artificialBlink = null; | ||
} | ||
@@ -30,0 +32,0 @@ get disabled() { |
@@ -53,10 +53,12 @@ import { MinitelObject } from '../abstract/minitelobject.js'; | ||
if (val) { | ||
if (this.minitel.focusedObj) | ||
if (this.minitel.focusedObj && this.minitel.focusedObj !== this) | ||
this.minitel.focusedObj.focused = false; | ||
if (this._focused !== val) | ||
(_b = (_a = this.attributes).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a); | ||
this._focused = true; | ||
(_b = (_a = this.attributes).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a); | ||
} | ||
else { | ||
if (this._focused !== val) | ||
(_d = (_c = this.attributes).onBlur) === null || _d === void 0 ? void 0 : _d.call(_c); | ||
this._focused = false; | ||
(_d = (_c = this.attributes).onBlur) === null || _d === void 0 ? void 0 : _d.call(_c); | ||
} | ||
@@ -63,0 +65,0 @@ } |
@@ -89,3 +89,2 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
current = current.next; | ||
debugger; | ||
} | ||
@@ -139,2 +138,3 @@ if (current) { | ||
if (err instanceof InvalidRender) { | ||
console.error(err); | ||
return this.renderString(); | ||
@@ -153,2 +153,3 @@ } | ||
let lastChar = null; | ||
// console.log(this.previousRender.toString()); | ||
for (let lineIdx in renderGrid.grid) { | ||
@@ -180,3 +181,3 @@ if (+lineIdx === 0 && this.settings.statusBar) | ||
lastAttributes = char.attributes; | ||
outputString.push(typeof char.char === 'string' ? char.char : ['', ' '][char.delta[0]]); | ||
outputString.push(typeof char.char === 'string' ? char.char : ['', ' '].at(char.delta[0])); | ||
skippedACharCounter = 0; | ||
@@ -219,3 +220,4 @@ } | ||
const isInTree = this.has(this.focusedObj); | ||
this.focusedObj.focused = isInTree; | ||
if (this.focusedObj.focused !== isInTree) | ||
this.focusedObj.focused = isInTree; | ||
if (isInTree) | ||
@@ -228,3 +230,3 @@ return; | ||
this.focusedObj = focusables[oneWithAutofocusIdx]; | ||
if (this.focusedObj) | ||
if (this.focusedObj && !this.focusedObj.focused) | ||
this.focusedObj.focused = true; | ||
@@ -243,3 +245,3 @@ } | ||
curr %= focusables.length; | ||
if (this.focusedObj) | ||
if (this.focusedObj && this.focusedObj.focused) | ||
this.focusedObj.focused = false; | ||
@@ -246,0 +248,0 @@ this.focusedObj = focusables[curr]; |
@@ -7,2 +7,3 @@ import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { Span } from './span.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
export declare class Paragraph extends MinitelObject { | ||
@@ -15,3 +16,4 @@ children: (TextNode | Span)[]; | ||
}; | ||
mapLocation(attributes: MinitelObjectAttributes, inheritMe: Partial<MinitelObjectAttributes>, nextNode: MinitelObject<MinitelObjectAttributes, Record<string, any[]>>, nodes: MinitelObject<MinitelObjectAttributes, Record<string, any[]>>[], weAt: number): LocationDescriptor; | ||
render(attributes: MinitelObjectAttributes, inheritMe: Partial<MinitelObjectAttributes>): RichCharGrid; | ||
} |
@@ -31,2 +31,25 @@ import { MinitelObject } from '../abstract/minitelobject.js'; | ||
} | ||
mapLocation(attributes, inheritMe, nextNode, nodes, weAt) { | ||
const originalLocationDescriptor = nextNode.mapLocationWrapper(inheritMe, {}, nodes, weAt); | ||
const lines = [new RichCharGrid([[]])]; // Again, if someone smarter than me can figure out an elegant way, suit urself | ||
for (let child of this.children) { | ||
if (child === nextNode) { | ||
originalLocationDescriptor.x += lines.at(-1).width; | ||
originalLocationDescriptor.y += lines.length; | ||
return originalLocationDescriptor; | ||
} | ||
const render = child.renderLines(inheritMe, { | ||
width: attributes.width, | ||
forcedIndent: lines.at(-1).width, | ||
}); | ||
const newMaxIdx = lines.length - 1; | ||
for (let lineIdx in render) { | ||
if (+lineIdx !== 0) { | ||
lines[newMaxIdx + +lineIdx] = new RichCharGrid([[]]); | ||
} | ||
lines[newMaxIdx + +lineIdx].mergeX(render[+lineIdx], 'end'); | ||
} | ||
} | ||
throw new Error("Something unexpected happened: Provided nextNode was not among my children!"); | ||
} | ||
render(attributes, inheritMe) { | ||
@@ -33,0 +56,0 @@ const fillChar = new RichChar(attributes.fillChar, attributes).noSize(); |
import { Focusable } from '../abstract/focusable.js'; | ||
import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
import { RichCharGrid } from '../richchargrid.js'; | ||
@@ -21,3 +23,3 @@ import { Container, ContainerAttributes } from './container.js'; | ||
blinkHandler(): void; | ||
constructor(children: never[] | undefined, attributes: Partial<ScrollableAttributes>, minitel: Minitel); | ||
constructor(children: MinitelObject<import("../types.js").MinitelObjectAttributes, Record<string, any[]>>[] | undefined, attributes: Partial<ScrollableAttributes>, minitel: Minitel); | ||
pushPrevScrollDelta(): void; | ||
@@ -31,2 +33,3 @@ popPrevScrollDelta(callback: (_arg0: [number, number]) => unknown): void; | ||
}; | ||
mapLocation(attributes: ScrollableAttributes, inheritMe: Partial<ScrollableAttributes>, nextNode: MinitelObject, nodes: MinitelObject[], weAt: number): LocationDescriptor; | ||
render(attributes: ScrollableAttributes, inheritMe: Partial<ScrollableAttributes>): RichCharGrid; | ||
@@ -33,0 +36,0 @@ get disabled(): boolean; |
@@ -18,10 +18,12 @@ import { RichChar } from '../richchar.js'; | ||
if (val) { | ||
if (this.minitel.focusedObj) | ||
if (this.minitel.focusedObj && this.minitel.focusedObj !== this) | ||
this.minitel.focusedObj.focused = false; | ||
if (this._focused !== val) | ||
(_b = (_a = this.attributes).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a); | ||
this._focused = true; | ||
(_b = (_a = this.attributes).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a); | ||
} | ||
else { | ||
if (this._focused !== val) | ||
(_d = (_c = this.attributes).onBlur) === null || _d === void 0 ? void 0 : _d.call(_c); | ||
this._focused = false; | ||
(_d = (_c = this.attributes).onBlur) === null || _d === void 0 ? void 0 : _d.call(_c); | ||
} | ||
@@ -140,2 +142,8 @@ } | ||
} | ||
mapLocation(attributes, inheritMe, nextNode, nodes, weAt) { | ||
const location = nextNode.mapLocationWrapper(inheritMe, {}, nodes, weAt); | ||
location.x -= this.scrollDelta[1]; | ||
location.y -= this.scrollDelta[0]; | ||
return location; | ||
} | ||
render(attributes, inheritMe) { | ||
@@ -158,3 +166,5 @@ // now its 3 am and i don't know how i'll read back | ||
const width = attributes.width != null && attributes.overflowX === 'hidden' | ||
? attributes.width - 1 | ||
? attributes.overflowY === 'noscrollbar' | ||
? attributes.width | ||
: attributes.width - 1 | ||
: null; | ||
@@ -174,3 +184,7 @@ renderAttributes = Object.assign(Object.assign({}, attributes), { width, height: null }); | ||
if (!autoedX) { | ||
const height = attributes.height != null ? attributes.height - 1 : null; | ||
const height = attributes.height != null // && attributes.overflowX === 'hidden' // we already know that | ||
? attributes.overflowX === 'noscrollbar' | ||
? attributes.height | ||
: attributes.height - 1 | ||
: null; | ||
renderAttributes = Object.assign(Object.assign({}, attributes), { height, width: null }); | ||
@@ -177,0 +191,0 @@ } |
import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
import { RichCharGrid } from '../richchargrid.js'; | ||
@@ -8,3 +9,3 @@ import { Align, MinitelObjectAttributes } from '../types.js'; | ||
defaultAttributes: XJoinAttributes; | ||
constructor(children: MinitelObject[], attributes: Partial<MinitelObjectAttributes>, minitel: Minitel); | ||
constructor(children: MinitelObject[], attributes: Partial<XJoinAttributes>, minitel: Minitel); | ||
getDimensions(attributes: XJoinAttributes, inheritMe: Partial<XJoinAttributes>): { | ||
@@ -14,2 +15,3 @@ width: number; | ||
}; | ||
mapLocation(attributes: XJoinAttributes, inheritMe: Partial<XJoinAttributes>, nextNode: MinitelObject, nodes: MinitelObject<MinitelObjectAttributes, Record<string, any[]>>[], weAt: number): LocationDescriptor; | ||
render(attributes: XJoinAttributes, inheritMe: Partial<XJoinAttributes>): RichCharGrid; | ||
@@ -16,0 +18,0 @@ } |
import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
import { RichChar } from '../richchar.js'; | ||
import { RichCharGrid } from '../richchargrid.js'; | ||
import { getDeltaFromSetting } from '../utils.js'; | ||
import { alignInvrt } from '../utils.js'; | ||
@@ -48,2 +50,83 @@ export class XJoin extends MinitelObject { | ||
} | ||
mapLocation(attributes, inheritMe, nextNode, nodes, weAt) { | ||
const heightIfStretch = attributes.height || this.children.reduce((p, c) => { | ||
const h = c.getDimensionsWrapper(inheritMe).height; | ||
if (h == null) | ||
return p; | ||
return Math.max(p, h); | ||
}, -Infinity); | ||
let cumulatedWidth = 0; | ||
let nextMapLocation = null; | ||
const rendersNoFlexGrow = this.children.map((v) => { | ||
if (v.attributes.flexGrow) | ||
return null; | ||
const newOptions = Object.assign({}, (attributes.heightAlign === 'stretch' ? { height: heightIfStretch } : {})); | ||
if (v === nextNode) | ||
nextMapLocation = v.mapLocationWrapper(inheritMe, newOptions, nodes, weAt); | ||
const render = v.getDimensionsWrapper(inheritMe, newOptions); | ||
cumulatedWidth += render.width; | ||
return [v, render]; | ||
}); | ||
const flexGrowTotal = this.children.reduce((p, c) => p + +(c.attributes.flexGrow || 0), 0); | ||
const remainingSpace = attributes.width != null ? attributes.width - cumulatedWidth : null; | ||
const unitOfFlexGrowSpace = remainingSpace != null ? remainingSpace / flexGrowTotal : null; | ||
let usedRemainingSpace = 0; | ||
const rendersYesFlexGrow = this.children.map((v) => { | ||
if (!v.attributes.flexGrow) | ||
return null; | ||
let newOptions = {}; | ||
if (unitOfFlexGrowSpace != null && remainingSpace != null) { | ||
const prevUsedRemSpace = usedRemainingSpace; | ||
usedRemainingSpace += unitOfFlexGrowSpace; | ||
newOptions = Object.assign(Object.assign({}, (attributes.heightAlign === 'stretch' ? { height: heightIfStretch } : {})), { width: Math.round(usedRemainingSpace) - Math.round(prevUsedRemSpace) }); | ||
} | ||
if (v === nextNode) | ||
nextMapLocation = v.mapLocationWrapper(inheritMe, newOptions, nodes, weAt); | ||
return [v, v.getDimensionsWrapper(inheritMe, newOptions)]; | ||
}); | ||
if (!(nextMapLocation instanceof LocationDescriptor)) | ||
throw new Error('nextNode was not within children; this is fatal for xjoin'); | ||
const renders = rendersNoFlexGrow.map((v, i) => v != null ? v : rendersYesFlexGrow[i]); | ||
const height = attributes.heightAlign === 'stretch' | ||
? heightIfStretch | ||
: attributes.height || Math.max(...renders.map((v) => v[1].height)); | ||
const contentsWidth = renders.reduce((c, v) => c + v[1].width, 0); | ||
// space-between: w / (n - 1) | ||
// space-around: w / n | ||
// space-evenly: w / (n + 1) | ||
let gapWidth; | ||
if (typeof attributes.gap === 'number') { | ||
gapWidth = attributes.gap; | ||
} | ||
else if (attributes.width != null) { | ||
const mappingTable = { | ||
'space-between': renders.length - 1, | ||
'space-around': renders.length, | ||
'space-evenly': renders.length + 1, | ||
}; | ||
gapWidth = (attributes.width - contentsWidth) / mappingTable[attributes.gap]; | ||
} | ||
else { | ||
gapWidth = 0; | ||
} | ||
let gapCumul = 0; | ||
let xCumul = 0; | ||
for (let render of renders) { | ||
if (render !== renders[0]) { | ||
const lastCumul = gapCumul; | ||
gapCumul += gapWidth; | ||
xCumul += Math.round(gapCumul) - Math.round(lastCumul); | ||
} | ||
if (render[0] === nextNode) { | ||
if (attributes.heightAlign !== 'stretch') { | ||
nextMapLocation.y += getDeltaFromSetting(nextMapLocation.h, height, alignInvrt[attributes.heightAlign]); | ||
} | ||
nextMapLocation.x += xCumul; | ||
} | ||
else { | ||
xCumul += render[1].width; | ||
} | ||
} | ||
return nextMapLocation; | ||
} | ||
render(attributes, inheritMe) { | ||
@@ -50,0 +133,0 @@ const fillChar = new RichChar(attributes.fillChar, attributes).noSize(); |
import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
import { RichCharGrid } from '../richchargrid.js'; | ||
@@ -8,3 +9,3 @@ import { Align, MinitelObjectAttributes } from '../types.js'; | ||
defaultAttributes: YJoinAttributes; | ||
constructor(children: MinitelObject[], attributes: Partial<MinitelObjectAttributes>, minitel: Minitel); | ||
constructor(children: MinitelObject[], attributes: Partial<YJoinAttributes>, minitel: Minitel); | ||
getDimensions(attributes: YJoinAttributes, inheritMe: Partial<YJoinAttributes>): { | ||
@@ -14,2 +15,3 @@ width: number; | ||
}; | ||
mapLocation(attributes: YJoinAttributes, inheritMe: Partial<YJoinAttributes>, nextNode: MinitelObject<MinitelObjectAttributes, Record<string, any[]>>, nodes: MinitelObject[], weAt: number): LocationDescriptor; | ||
render(attributes: YJoinAttributes, inheritMe: Partial<YJoinAttributes>): RichCharGrid; | ||
@@ -16,0 +18,0 @@ } |
import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
import { RichChar } from '../richchar.js'; | ||
import { RichCharGrid } from '../richchargrid.js'; | ||
import { getDeltaFromSetting } from '../utils.js'; | ||
import { alignInvrt } from '../utils.js'; | ||
@@ -48,2 +50,83 @@ export class YJoin extends MinitelObject { | ||
} | ||
mapLocation(attributes, inheritMe, nextNode, nodes, weAt) { | ||
const widthIfStretch = attributes.width || this.children.reduce((p, c) => { | ||
const w = c.getDimensionsWrapper(inheritMe).width; | ||
if (w == null) | ||
return p; | ||
return Math.max(p, w); | ||
}, -Infinity); | ||
let cumulatedHeight = 0; | ||
let nextMapLocation = null; | ||
const rendersNoFlexGrow = this.children.map((v) => { | ||
if (v.attributes.flexGrow) | ||
return null; | ||
const newOptions = Object.assign({}, (attributes.widthAlign === 'stretch' ? { width: widthIfStretch } : {})); | ||
if (v === nextNode) | ||
nextMapLocation = v.mapLocationWrapper(inheritMe, newOptions, nodes, weAt); | ||
const render = v.getDimensionsWrapper(inheritMe, newOptions); | ||
cumulatedHeight += render.width; | ||
return [v, render]; | ||
}); | ||
const flexGrowTotal = this.children.reduce((p, c) => p + +(c.attributes.flexGrow || 0), 0); | ||
const remainingSpace = attributes.height != null ? attributes.height - cumulatedHeight : null; | ||
const unitOfFlexGrowSpace = remainingSpace != null ? remainingSpace / flexGrowTotal : null; | ||
let usedRemainingSpace = 0; | ||
const rendersYesFlexGrow = this.children.map((v) => { | ||
if (!v.attributes.flexGrow) | ||
return null; | ||
let newOptions = {}; | ||
if (unitOfFlexGrowSpace != null && remainingSpace != null) { | ||
const prevUsedRemSpace = usedRemainingSpace; | ||
usedRemainingSpace += unitOfFlexGrowSpace; | ||
newOptions = Object.assign(Object.assign({}, (attributes.widthAlign === 'stretch' ? { width: widthIfStretch } : {})), { height: Math.round(usedRemainingSpace) - Math.round(prevUsedRemSpace) }); | ||
} | ||
if (v === nextNode) | ||
nextMapLocation = v.mapLocationWrapper(inheritMe, newOptions, nodes, weAt); | ||
return [v, v.getDimensionsWrapper(inheritMe, newOptions)]; | ||
}); | ||
if (!(nextMapLocation instanceof LocationDescriptor)) | ||
throw new Error('nextNode was not within children; this is fatal for xjoin'); | ||
const renders = rendersNoFlexGrow.map((v, i) => v != null ? v : rendersYesFlexGrow[i]); | ||
const width = attributes.widthAlign === 'stretch' | ||
? widthIfStretch | ||
: attributes.width || Math.max(...renders.map((v) => v[1].width)); | ||
const contentsHeight = renders.reduce((c, v) => c + v[1].height, 0); | ||
// space-between: w / (n - 1) | ||
// space-around: w / n | ||
// space-evenly: w / (n + 1) | ||
let gapWidth; | ||
if (typeof attributes.gap === 'number') { | ||
gapWidth = attributes.gap; | ||
} | ||
else if (attributes.height != null) { | ||
const mappingTable = { | ||
'space-between': renders.length - 1, | ||
'space-around': renders.length, | ||
'space-evenly': renders.length + 1, | ||
}; | ||
gapWidth = (attributes.height - contentsHeight) / mappingTable[attributes.gap]; | ||
} | ||
else { | ||
gapWidth = 0; | ||
} | ||
let gapCumul = 0; | ||
let yCumul = 0; | ||
for (let render of renders) { | ||
if (render !== renders[0]) { | ||
const lastCumul = gapCumul; | ||
gapCumul += gapWidth; | ||
yCumul += Math.round(gapCumul) - Math.round(lastCumul); | ||
} | ||
if (render[0] === nextNode) { | ||
if (attributes.widthAlign !== 'stretch') { | ||
nextMapLocation.x += getDeltaFromSetting(nextMapLocation.w, width, alignInvrt[attributes.widthAlign]); | ||
} | ||
nextMapLocation.y += yCumul; | ||
} | ||
else { | ||
yCumul += render[1].height; | ||
} | ||
} | ||
return nextMapLocation; | ||
} | ||
render(attributes, inheritMe) { | ||
@@ -50,0 +133,0 @@ const fillChar = new RichChar(attributes.fillChar, attributes).noSize(); |
import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
import { RichCharGrid } from '../richchargrid.js'; | ||
@@ -13,2 +14,3 @@ import { Align, MinitelObjectAttributes } from '../types.js'; | ||
}; | ||
mapLocation(attributes: ZJoinAttributes, inheritMe: Partial<ZJoinAttributes>, nextNode: MinitelObject, nodes: MinitelObject[], weAt: number): LocationDescriptor; | ||
render(attributes: ZJoinAttributes, inheritMe: Partial<ZJoinAttributes>): RichCharGrid; | ||
@@ -15,0 +17,0 @@ } |
import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { RichChar } from '../richchar.js'; | ||
import { RichCharGrid } from '../richchargrid.js'; | ||
import { getDeltaFromSetting } from '../utils.js'; | ||
export class ZJoin extends MinitelObject { | ||
@@ -15,2 +16,18 @@ constructor(children, attributes, minitel) { | ||
} | ||
mapLocation(attributes, inheritMe, nextNode, nodes, weAt) { | ||
const renders = this.children.map((v) => [v, v.getDimensionsWrapper(inheritMe, { | ||
width: attributes.width, | ||
height: attributes.height, | ||
})]); | ||
const maxWidth = Math.max(...renders.map((v) => v[1].width)); | ||
const maxHeight = Math.max(...renders.map((v) => v[1].height)); | ||
const relevant = renders.find((v) => v[0] === nextNode); | ||
const prevLocation = nextNode.mapLocationWrapper(inheritMe, { | ||
width: attributes.width, | ||
height: attributes.height, | ||
}, nodes, weAt); | ||
prevLocation.x += getDeltaFromSetting(relevant[1].width, maxWidth, attributes.widthAlign); | ||
prevLocation.y += getDeltaFromSetting(relevant[1].height, maxWidth, attributes.heightAlign); | ||
return prevLocation; | ||
} | ||
render(attributes, inheritMe) { | ||
@@ -17,0 +34,0 @@ const fillChar = new RichChar(attributes.fillChar, attributes).noSize(); |
@@ -18,6 +18,8 @@ import { LocationDescriptors } from './locationdescriptor.js'; | ||
pad(fullPad: FullPadding, fillChar: RichChar<string>): this; | ||
forceIntegrityOn(y: number, x: number): void; | ||
forceIntegrityOnTheSides(): void; | ||
cutHeight(height: number, heightAlign: Align): this; | ||
cutWidth(width: number, widthAlign: Align): this; | ||
setHeight(height: number, heightAlign: Align, char?: RichChar<string>): this; | ||
setWidth(width: number, widthAlign: Align, char?: RichChar<string>): this; | ||
setHeight(height: number, heightAlign: Align, char: RichChar<string>): this; | ||
setWidth(width: number, widthAlign: Align, char: RichChar<string>): this; | ||
mergeLocationDescriptors(operand: RichCharGrid): void; | ||
@@ -24,0 +26,0 @@ mergeY(operand: RichCharGrid, heightAlign?: string): void; |
@@ -93,2 +93,32 @@ import { LocationDescriptors } from './locationdescriptor.js'; | ||
} | ||
forceIntegrityOn(y, x) { | ||
const cell = this.grid[y][x]; | ||
if (!cell.delta) | ||
return; | ||
const newY = y + cell.delta[0]; | ||
const newX = x + cell.delta[1]; | ||
if (!(newY in this.grid) || !(newX in this.grid[newY])) { | ||
this.grid[y][x] = cell.copy(); | ||
this.grid[y][x].delta = undefined; | ||
this.grid[y][x].actualChar = undefined; | ||
if (x === 0) | ||
this.grid[y][x].char = '<'; | ||
else | ||
this.grid[y][x].char = '>'; | ||
} | ||
} | ||
forceIntegrityOnTheSides() { | ||
for (let side = 0; side < 2; side += 1) { | ||
for (let y = 0; y < this.height; y += 1) { | ||
const currY = y; | ||
const currX = side * (this.width - 1); | ||
this.forceIntegrityOn(currY, currX); | ||
} | ||
for (let x = 0; x < this.width; x += 1) { | ||
const currX = x; | ||
const currY = side * (this.height - 1); | ||
this.forceIntegrityOn(currY, currX); | ||
} | ||
} | ||
} | ||
cutHeight(height, heightAlign) { | ||
@@ -105,2 +135,3 @@ const prevHeight = this.height; | ||
this.locationDescriptors.cut(this.height, this.width); | ||
this.forceIntegrityOnTheSides(); | ||
return this; | ||
@@ -111,2 +142,3 @@ case 'middle': | ||
this.cutHeight(height, 'end'); | ||
this.forceIntegrityOnTheSides(); | ||
return this; | ||
@@ -126,2 +158,3 @@ } | ||
this.locationDescriptors.cut(this.height, this.width); | ||
this.forceIntegrityOnTheSides(); | ||
return this; | ||
@@ -132,6 +165,7 @@ case 'middle': | ||
this.cutWidth(width, 'end'); | ||
this.forceIntegrityOnTheSides(); | ||
return this; | ||
} | ||
} | ||
setHeight(height, heightAlign, char = new RichChar(' ')) { | ||
setHeight(height, heightAlign, char) { | ||
if (this.height === height) | ||
@@ -156,3 +190,3 @@ return this; | ||
} | ||
setWidth(width, widthAlign, char = new RichChar(' ')) { | ||
setWidth(width, widthAlign, char) { | ||
if (this.width === width) | ||
@@ -159,0 +193,0 @@ return this; |
@@ -11,2 +11,3 @@ export interface CharAttributes { | ||
} | ||
export type CharAttributesWithoutDouble = Omit<CharAttributes, 'doubleHeight' | 'doubleWidth'>; | ||
export type ApplicableCharAttributes = Omit<CharAttributes, 'charset'>; | ||
@@ -13,0 +14,0 @@ export type Align = 'start' | 'middle' | 'end'; |
@@ -10,1 +10,2 @@ import { Align, FullPadding, MinitelObjectAttributes, Padding } from './types.js'; | ||
export declare function toBitArray(char: string): number[]; | ||
export declare function getDeltaFromSetting(size: number, setInto: number, align: Align): number; |
@@ -7,3 +7,3 @@ export const alignInvrt = { | ||
export function inheritedProps(props) { | ||
const inheritedProps = ['fillChar', 'fg', 'textAlign', 'bg', 'underline', 'noBlink', 'invert', 'doubleWidth', 'doubleHeight', 'wrap']; | ||
const inheritedProps = ['fillChar', 'fg', 'textAlign', 'bg', 'underline', 'doubleWidth', 'doubleHeight', 'noBlink', 'invert', 'wrap']; | ||
const result = {}; | ||
@@ -45,1 +45,8 @@ let inheritedProp; | ||
} | ||
export function getDeltaFromSetting(size, setInto, align) { | ||
if (align === 'start') | ||
return 0; | ||
if (align === 'end') | ||
return setInto - size; | ||
return Math.floor((setInto - size) / 2); | ||
} |
{ | ||
"name": "minitel-standalone", | ||
"version": "1.10.2", | ||
"version": "2.0.0", | ||
"description": "A standalone package for minitel components", | ||
@@ -24,3 +24,5 @@ "main": "dist/index.js", | ||
"@types/node": "^20.12.12", | ||
"typescript": "^5.4.5" | ||
"typescript": "^5.4.5", | ||
"ws": "^8.18.0", | ||
"ws-duplex-bridge": "^1.0.1" | ||
}, | ||
@@ -27,0 +29,0 @@ "dependencies": { |
@@ -119,10 +119,12 @@ import { EventEmitter } from 'node:events'; | ||
if (attributes.width != null) result.setWidth(attributes.width, 'end'); | ||
if (attributes.height != null) result.setHeight(attributes.height, 'end'); | ||
if (attributes.width != null) result.setWidth(attributes.width, 'end', fillChar); | ||
if (attributes.height != null) result.setHeight(attributes.height, 'end', fillChar); | ||
result.pad(pad, fillChar); | ||
// Descriptor before pad, is this the right choice? | ||
// Future you: Yes. Yes it is. | ||
// Future you 5 minutes later: Actually you know what | ||
if (this.keepElmDesc) result.locationDescriptors.add(this, new LocationDescriptor(0, 0, result.width, result.height)); | ||
result.pad(pad, fillChar); | ||
return result; | ||
@@ -149,2 +151,79 @@ } | ||
} | ||
mapLocation(attributes: T, inheritMe: Partial<T>, nextNode: MinitelObject, nodes: MinitelObject[], weAt: number): LocationDescriptor { | ||
return nextNode.mapLocationWrapper(inheritMe, {}, nodes, weAt) | ||
} | ||
mapLocationWrapper(inheritedAttributes: Partial<T>, forcedAttributes: Partial<T>, nodes: MinitelObject[], weAt: number): LocationDescriptor { | ||
const nextNode: MinitelObject | undefined = nodes[weAt + 1]; | ||
const nextNodeIdx = nextNode && this.children.indexOf(nextNode); | ||
if (nextNodeIdx === -1) { | ||
throw new Error(`Next node was not found in children. This behaviour is unexpected; please contact the owner of minitel-standalone.`); | ||
} | ||
const attributes: T = { | ||
...this.defaultAttributes, | ||
...inheritedAttributes, | ||
...this.attributes, | ||
...forcedAttributes, | ||
}; | ||
const pad = padding.normalise(attributes.pad); | ||
attributes.width = attributes.width != null ? padding.exludeX(attributes.width, pad) : null; | ||
attributes.height = attributes.height != null ? padding.exludeY(attributes.height, pad) : null; | ||
if (!nextNode) { | ||
const dimensions = this.getDimensionsWrapper(attributes, inheritedProps({ | ||
...inheritedAttributes, | ||
...this.attributes, | ||
...forcedAttributes, | ||
})); | ||
return new LocationDescriptor(0, 0, dimensions.width, dimensions.height); | ||
} | ||
let result = this.mapLocation(attributes, inheritedProps({ | ||
...inheritedAttributes, | ||
...this.attributes, | ||
...forcedAttributes, | ||
}), nextNode, nodes, weAt + 1); | ||
result.x += pad[0]; | ||
result.y += pad[3]; | ||
return result; | ||
} | ||
get parentList(): MinitelObject[] { | ||
return [...(this.parent?.parentList || []), this]; | ||
} | ||
scrollIntoView(context?: MinitelObject & { scrollDelta: [number, number] }): void { | ||
if (!context) { | ||
const parentList = this.parentList.filter((v): v is MinitelObject & { scrollDelta: [number, number] } => 'scrollDelta' in v); | ||
for (let i = 0; i < parentList.length - 1; i += 1) parentList[i + 1].scrollIntoView(parentList[i]); | ||
this.scrollIntoView(parentList.at(-1)); | ||
return; | ||
} | ||
const pathToThis = this.parentList; | ||
const pathToScrollable = context.parentList; | ||
const thisPos = this.minitel.mapLocationWrapper({}, {}, pathToThis, 0); | ||
const scrollablePos = this.minitel.mapLocationWrapper({}, {}, pathToScrollable, 0); | ||
const [relY, relX] = [thisPos.y - scrollablePos.y, thisPos.x - scrollablePos.x]; | ||
if (relY < 0) { | ||
context.scrollDelta[0] += relY; | ||
} else if (relY + thisPos.h > scrollablePos.h) { | ||
context.scrollDelta[0] -= scrollablePos.h - (relY + thisPos.h); | ||
} | ||
if (relX < 0) { | ||
context.scrollDelta[1] += relX; | ||
} else if (relX + thisPos.w > scrollablePos.w) { | ||
context.scrollDelta[1] -= scrollablePos.w - (relX + thisPos.w); | ||
} | ||
} | ||
} |
@@ -7,2 +7,3 @@ import { MinitelObject } from './minitelobject.js'; | ||
import type { Minitel } from '../index.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
@@ -102,2 +103,6 @@ export class TextNode extends MinitelObject { | ||
} | ||
mapLocation(attributes: MinitelObjectAttributes, inheritMe: Partial<MinitelObjectAttributes>, nextNode: MinitelObject): LocationDescriptor { | ||
throw new Error('TextNode doesn\'t have children, and therefore can\'t mapLocation'); | ||
} | ||
} |
@@ -15,3 +15,3 @@ import { MinitelObject } from '../abstract/minitelobject.js'; | ||
defaultAttributes = Container.defaultAttributes as T; | ||
constructor(children = [], attributes: Partial<T>, minitel: Minitel) { | ||
constructor(children: MinitelObject[] = [], attributes: Partial<T>, minitel: Minitel) { | ||
if (children.length > 1) throw new Error('Container must only include one element'); | ||
@@ -18,0 +18,0 @@ super([], attributes, minitel); |
import { FocusableAttributes as FocusableIfaceAttributes, Focusable as FocusableIface } from '../abstract/focusable.js'; | ||
import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { Container, ContainerAttributes } from './container.js'; | ||
@@ -20,12 +21,12 @@ import type { Minitel } from './minitel.js'; | ||
keepElmDesc: true = true; | ||
private artificialBlink: NodeJS.Timeout | null = null; | ||
set focused(val) { | ||
if (this._focused !== val) this.minitel.invalidateRender(); | ||
if (val) { | ||
if (this.minitel.focusedObj) this.minitel.focusedObj.focused = false; | ||
// console.log(this.minitel.focusedObj, this.minitel.focusedObj === this); | ||
if (this.minitel.focusedObj && this.minitel.focusedObj !== this) this.minitel.focusedObj.focused = false; | ||
if (this._focused !== val) this.attributes.onFocus?.(); | ||
this._focused = true; | ||
this.attributes.onFocus?.(); | ||
} else { | ||
if (this._focused !== val) this.attributes.onBlur?.(); | ||
this._focused = false; | ||
this.attributes.onBlur?.(); | ||
} | ||
@@ -36,3 +37,3 @@ } | ||
} | ||
constructor(children = [], attributes: Partial<FocusableAttributes>, minitel: Minitel) { | ||
constructor(children: MinitelObject[] = [], attributes: Partial<FocusableAttributes>, minitel: Minitel) { | ||
super(children, attributes, minitel); | ||
@@ -39,0 +40,0 @@ } |
@@ -73,8 +73,8 @@ import { Focusable, FocusableAttributes } from '../abstract/focusable.js'; | ||
if (val) { | ||
if (this.minitel.focusedObj) this.minitel.focusedObj.focused = false; | ||
if (this.minitel.focusedObj && this.minitel.focusedObj !== this) this.minitel.focusedObj.focused = false; | ||
if (this._focused !== val) this.attributes.onFocus?.(); | ||
this._focused = true; | ||
this.attributes.onFocus?.(); | ||
} else { | ||
if (this._focused !== val) this.attributes.onBlur?.(); | ||
this._focused = false; | ||
this.attributes.onBlur?.(); | ||
} | ||
@@ -81,0 +81,0 @@ } |
import { Duplex } from 'stream'; | ||
import { Container, ContainerAttributes } from './container.js'; | ||
import { RichCharGrid } from '../richchargrid.js'; | ||
import { CharAttributes, MinitelObjectAttributes } from '../types.js'; | ||
import { CharAttributes } from '../types.js'; | ||
import { SingletonArray } from '../singleton.js'; | ||
@@ -127,3 +127,2 @@ import { MinitelObject } from '../abstract/minitelobject.js'; | ||
current = current.next; | ||
debugger; | ||
} | ||
@@ -176,2 +175,3 @@ if (current) { | ||
if (err instanceof InvalidRender) { | ||
console.error(err); | ||
return this.renderString(); | ||
@@ -194,2 +194,4 @@ } else { | ||
// console.log(this.previousRender.toString()); | ||
for (let lineIdx in renderGrid.grid) { | ||
@@ -240,3 +242,3 @@ if (+lineIdx === 0 && this.settings.statusBar) outputString.push('\x1f\x40\x41'); | ||
outputString.push(typeof char.char === 'string' ? char.char : ['', ' '][char.delta[0]]) | ||
outputString.push(typeof char.char === 'string' ? char.char : ['', ' '].at(char.delta[0])!) | ||
skippedACharCounter = 0; | ||
@@ -286,3 +288,3 @@ } | ||
const isInTree = this.has(this.focusedObj); | ||
this.focusedObj.focused = isInTree; | ||
if (this.focusedObj.focused !== isInTree) this.focusedObj.focused = isInTree; | ||
if (isInTree) return; | ||
@@ -295,3 +297,3 @@ this.focusedObj = null; | ||
if (this.focusedObj) this.focusedObj.focused = true; | ||
if (this.focusedObj && !this.focusedObj.focused) this.focusedObj.focused = true; | ||
} | ||
@@ -311,3 +313,3 @@ focusDelta(delta: 1 | -1) { | ||
if (this.focusedObj) this.focusedObj.focused = false; | ||
if (this.focusedObj && this.focusedObj.focused) this.focusedObj.focused = false; | ||
this.focusedObj = focusables[curr]; | ||
@@ -314,0 +316,0 @@ this.focusedObj.focused = true; |
@@ -9,2 +9,3 @@ import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { Span } from './span.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
@@ -41,2 +42,26 @@ export class Paragraph extends MinitelObject { | ||
} | ||
mapLocation(attributes: MinitelObjectAttributes, inheritMe: Partial<MinitelObjectAttributes>, nextNode: MinitelObject<MinitelObjectAttributes, Record<string, any[]>>, nodes: MinitelObject<MinitelObjectAttributes, Record<string, any[]>>[], weAt: number): LocationDescriptor { | ||
const originalLocationDescriptor = nextNode.mapLocationWrapper(inheritMe, {}, nodes, weAt); | ||
const lines = [new RichCharGrid([[]])]; // Again, if someone smarter than me can figure out an elegant way, suit urself | ||
for (let child of this.children) { | ||
if (child === nextNode) { | ||
originalLocationDescriptor.x += lines.at(-1)!.width; | ||
originalLocationDescriptor.y += lines.length; | ||
return originalLocationDescriptor; | ||
} | ||
const render = child.renderLines(inheritMe, { | ||
width: attributes.width, | ||
forcedIndent: lines.at(-1)!.width, | ||
}); | ||
const newMaxIdx = lines.length - 1; | ||
for (let lineIdx in render) { | ||
if (+lineIdx !== 0) { | ||
lines[newMaxIdx + +lineIdx] = new RichCharGrid([[]]); | ||
} | ||
lines[newMaxIdx + +lineIdx].mergeX(render[+lineIdx], 'end'); | ||
} | ||
} | ||
throw new Error("Something unexpected happened: Provided nextNode was not among my children!"); | ||
} | ||
render(attributes: MinitelObjectAttributes, inheritMe: Partial<MinitelObjectAttributes>) { | ||
@@ -43,0 +68,0 @@ const fillChar = new RichChar(attributes.fillChar, attributes).noSize(); |
import { Focusable } from '../abstract/focusable.js'; | ||
import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
import { RichChar } from '../richchar.js'; | ||
@@ -42,8 +44,8 @@ import { RichCharGrid } from '../richchargrid.js'; | ||
if (val) { | ||
if (this.minitel.focusedObj) this.minitel.focusedObj.focused = false; | ||
if (this.minitel.focusedObj && this.minitel.focusedObj !== this) this.minitel.focusedObj.focused = false; | ||
if (this._focused !== val) this.attributes.onFocus?.(); | ||
this._focused = true; | ||
this.attributes.onFocus?.(); | ||
} else { | ||
if (this._focused !== val) this.attributes.onBlur?.(); | ||
this._focused = false; | ||
this.attributes.onBlur?.(); | ||
} | ||
@@ -67,3 +69,3 @@ } | ||
} | ||
constructor(children = [], attributes: Partial<ScrollableAttributes>, minitel: Minitel) { | ||
constructor(children: MinitelObject[] = [], attributes: Partial<ScrollableAttributes>, minitel: Minitel) { | ||
super(children, attributes, minitel); | ||
@@ -170,2 +172,9 @@ | ||
} | ||
mapLocation(attributes: ScrollableAttributes, inheritMe: Partial<ScrollableAttributes>, nextNode: MinitelObject, nodes: MinitelObject[], weAt: number): LocationDescriptor { | ||
const location = nextNode.mapLocationWrapper(inheritMe, {}, nodes, weAt); | ||
location.x -= this.scrollDelta[1]; | ||
location.y -= this.scrollDelta[0]; | ||
return location; | ||
} | ||
render(attributes: ScrollableAttributes, inheritMe: Partial<ScrollableAttributes>) { | ||
@@ -196,3 +205,5 @@ // now its 3 am and i don't know how i'll read back | ||
const width = attributes.width != null && attributes.overflowX === 'hidden' | ||
? attributes.width - 1 | ||
? attributes.overflowY === 'noscrollbar' | ||
? attributes.width | ||
: attributes.width - 1 | ||
: null; | ||
@@ -216,4 +227,8 @@ | ||
if (!autoedX) { | ||
const height = attributes.height != null ? attributes.height - 1 : null; | ||
const height = attributes.height != null // && attributes.overflowX === 'hidden' // we already know that | ||
? attributes.overflowX === 'noscrollbar' | ||
? attributes.height | ||
: attributes.height - 1 | ||
: null; | ||
renderAttributes = { ...attributes, height, width: null }; | ||
@@ -236,3 +251,2 @@ } | ||
const maxScrollSizeY = attributes.overflowX !== 'hidden' && attributes.overflowX !== 'noscrollbar' && !autoedX && attributes.height != null | ||
@@ -239,0 +253,0 @@ ? attributes.height - 1 |
import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
import { RichChar } from '../richchar.js'; | ||
import { RichCharGrid } from '../richchargrid.js'; | ||
import { Align, MinitelObjectAttributes } from '../types.js'; | ||
import { getDeltaFromSetting } from '../utils.js'; | ||
import { alignInvrt } from '../utils.js'; | ||
@@ -16,3 +18,3 @@ import type { Minitel } from './minitel.js'; | ||
defaultAttributes = XJoin.defaultAttributes; | ||
constructor(children: MinitelObject[], attributes: Partial<MinitelObjectAttributes>, minitel: Minitel) { | ||
constructor(children: MinitelObject[], attributes: Partial<XJoinAttributes>, minitel: Minitel) { | ||
super(children, attributes, minitel); | ||
@@ -71,2 +73,99 @@ } | ||
} | ||
mapLocation(attributes: XJoinAttributes, inheritMe: Partial<XJoinAttributes>, nextNode: MinitelObject, nodes: MinitelObject<MinitelObjectAttributes, Record<string, any[]>>[], weAt: number): LocationDescriptor { | ||
const heightIfStretch = attributes.height || this.children.reduce((p, c) => { | ||
const h = c.getDimensionsWrapper(inheritMe).height; | ||
if (h == null) return p; | ||
return Math.max(p, h); | ||
}, -Infinity); | ||
let cumulatedWidth = 0; | ||
let nextMapLocation: LocationDescriptor | null = null as LocationDescriptor | null; | ||
const rendersNoFlexGrow = this.children.map((v) => { | ||
if (v.attributes.flexGrow) return null; | ||
const newOptions = { | ||
...(attributes.heightAlign === 'stretch' ? { height: heightIfStretch } : {}), | ||
}; | ||
if (v === nextNode) nextMapLocation = v.mapLocationWrapper(inheritMe, newOptions, nodes, weAt); | ||
const render = v.getDimensionsWrapper(inheritMe, newOptions); | ||
cumulatedWidth += render.width; | ||
return [v, render]; | ||
}); | ||
const flexGrowTotal = this.children.reduce((p, c) => p + +(c.attributes.flexGrow || 0), 0); | ||
const remainingSpace = attributes.width != null ? attributes.width - cumulatedWidth : null; | ||
const unitOfFlexGrowSpace = remainingSpace != null ? remainingSpace / flexGrowTotal : null; | ||
let usedRemainingSpace = 0; | ||
const rendersYesFlexGrow = this.children.map((v) => { | ||
if (!v.attributes.flexGrow) return null; | ||
let newOptions = {}; | ||
if (unitOfFlexGrowSpace != null && remainingSpace != null) { | ||
const prevUsedRemSpace = usedRemainingSpace; | ||
usedRemainingSpace += unitOfFlexGrowSpace; | ||
newOptions = { | ||
...(attributes.heightAlign === 'stretch' ? { height: heightIfStretch } : {}), | ||
width: Math.round(usedRemainingSpace) - Math.round(prevUsedRemSpace), | ||
}; | ||
} | ||
if (v === nextNode) nextMapLocation = v.mapLocationWrapper(inheritMe, newOptions, nodes, weAt); | ||
return [v, v.getDimensionsWrapper(inheritMe, newOptions)]; | ||
}); | ||
if (!(nextMapLocation instanceof LocationDescriptor)) throw new Error('nextNode was not within children; this is fatal for xjoin'); | ||
const renders = rendersNoFlexGrow.map((v, i) => v != null ? v : rendersYesFlexGrow[i]) as [MinitelObject, { | ||
width: number; | ||
height: number; | ||
}][]; | ||
const height = attributes.heightAlign === 'stretch' | ||
? heightIfStretch | ||
: attributes.height || Math.max(...renders.map((v) => v[1].height)); | ||
const contentsWidth = renders.reduce((c, v) => c + v[1].width, 0); | ||
// space-between: w / (n - 1) | ||
// space-around: w / n | ||
// space-evenly: w / (n + 1) | ||
let gapWidth: number; | ||
if (typeof attributes.gap === 'number') { | ||
gapWidth = attributes.gap; | ||
} else if (attributes.width != null) { | ||
const mappingTable = { | ||
'space-between': renders.length - 1, | ||
'space-around': renders.length, | ||
'space-evenly': renders.length + 1, | ||
}; | ||
gapWidth = (attributes.width - contentsWidth) / mappingTable[attributes.gap]; | ||
} else { | ||
gapWidth = 0; | ||
} | ||
let gapCumul = 0; | ||
let xCumul = 0; | ||
for (let render of renders) { | ||
if (render !== renders[0]) { | ||
const lastCumul = gapCumul; | ||
gapCumul += gapWidth; | ||
xCumul += Math.round(gapCumul) - Math.round(lastCumul); | ||
} | ||
if (render[0] === nextNode) { | ||
if (attributes.heightAlign !== 'stretch') { | ||
nextMapLocation.y += getDeltaFromSetting(nextMapLocation.h, height, alignInvrt[attributes.heightAlign]); | ||
} | ||
nextMapLocation.x += xCumul; | ||
} else { | ||
xCumul += render[1].width; | ||
} | ||
} | ||
return nextMapLocation; | ||
} | ||
render(attributes: XJoinAttributes, inheritMe: Partial<XJoinAttributes>) { | ||
@@ -73,0 +172,0 @@ const fillChar = new RichChar(attributes.fillChar, attributes).noSize(); |
import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
import { RichChar } from '../richchar.js'; | ||
import { RichCharGrid } from '../richchargrid.js'; | ||
import { Align, MinitelObjectAttributes } from '../types.js'; | ||
import { getDeltaFromSetting } from '../utils.js'; | ||
import { alignInvrt, inheritedProps } from '../utils.js'; | ||
@@ -16,3 +18,3 @@ import type { Minitel } from './minitel.js'; | ||
defaultAttributes = YJoin.defaultAttributes; | ||
constructor(children: MinitelObject[], attributes: Partial<MinitelObjectAttributes>, minitel: Minitel) { | ||
constructor(children: MinitelObject[], attributes: Partial<YJoinAttributes>, minitel: Minitel) { | ||
super(children, attributes, minitel); | ||
@@ -71,2 +73,100 @@ } | ||
} | ||
mapLocation(attributes: YJoinAttributes, inheritMe: Partial<YJoinAttributes>, nextNode: MinitelObject<MinitelObjectAttributes, Record<string, any[]>>, nodes: MinitelObject[], weAt: number): LocationDescriptor { | ||
const widthIfStretch = attributes.width || this.children.reduce((p, c) => { | ||
const w = c.getDimensionsWrapper(inheritMe).width; | ||
if (w == null) return p; | ||
return Math.max(p, w); | ||
}, -Infinity); | ||
let cumulatedHeight = 0; | ||
let nextMapLocation: LocationDescriptor | null = null as LocationDescriptor | null; | ||
const rendersNoFlexGrow = this.children.map((v) => { | ||
if (v.attributes.flexGrow) return null; | ||
const newOptions = { | ||
...(attributes.widthAlign === 'stretch' ? { width: widthIfStretch } : {}), | ||
}; | ||
if (v === nextNode) nextMapLocation = v.mapLocationWrapper(inheritMe, newOptions, nodes, weAt); | ||
const render = v.getDimensionsWrapper(inheritMe, newOptions); | ||
cumulatedHeight += render.width; | ||
return [v, render]; | ||
}); | ||
const flexGrowTotal = this.children.reduce((p, c) => p + +(c.attributes.flexGrow || 0), 0); | ||
const remainingSpace = attributes.height != null ? attributes.height - cumulatedHeight : null; | ||
const unitOfFlexGrowSpace = remainingSpace != null ? remainingSpace / flexGrowTotal : null; | ||
let usedRemainingSpace = 0; | ||
const rendersYesFlexGrow = this.children.map((v) => { | ||
if (!v.attributes.flexGrow) return null; | ||
let newOptions = {}; | ||
if (unitOfFlexGrowSpace != null && remainingSpace != null) { | ||
const prevUsedRemSpace = usedRemainingSpace; | ||
usedRemainingSpace += unitOfFlexGrowSpace; | ||
newOptions = { | ||
...(attributes.widthAlign === 'stretch' ? { width: widthIfStretch } : {}), | ||
height: Math.round(usedRemainingSpace) - Math.round(prevUsedRemSpace), | ||
}; | ||
} | ||
if (v === nextNode) nextMapLocation = v.mapLocationWrapper(inheritMe, newOptions, nodes, weAt); | ||
return [v, v.getDimensionsWrapper(inheritMe, newOptions)]; | ||
}); | ||
if (!(nextMapLocation instanceof LocationDescriptor)) throw new Error('nextNode was not within children; this is fatal for xjoin'); | ||
const renders = rendersNoFlexGrow.map((v, i) => v != null ? v : rendersYesFlexGrow[i]) as [MinitelObject, { | ||
width: number; | ||
height: number; | ||
}][]; | ||
const width = attributes.widthAlign === 'stretch' | ||
? widthIfStretch | ||
: attributes.width || Math.max(...renders.map((v) => v[1].width)); | ||
const contentsHeight = renders.reduce((c, v) => c + v[1].height, 0); | ||
// space-between: w / (n - 1) | ||
// space-around: w / n | ||
// space-evenly: w / (n + 1) | ||
let gapWidth: number; | ||
if (typeof attributes.gap === 'number') { | ||
gapWidth = attributes.gap; | ||
} else if (attributes.height != null) { | ||
const mappingTable = { | ||
'space-between': renders.length - 1, | ||
'space-around': renders.length, | ||
'space-evenly': renders.length + 1, | ||
}; | ||
gapWidth = (attributes.height - contentsHeight) / mappingTable[attributes.gap]; | ||
} else { | ||
gapWidth = 0; | ||
} | ||
let gapCumul = 0; | ||
let yCumul = 0; | ||
for (let render of renders) { | ||
if (render !== renders[0]) { | ||
const lastCumul = gapCumul; | ||
gapCumul += gapWidth; | ||
yCumul += Math.round(gapCumul) - Math.round(lastCumul); | ||
} | ||
if (render[0] === nextNode) { | ||
if (attributes.widthAlign !== 'stretch') { | ||
nextMapLocation.x += getDeltaFromSetting(nextMapLocation.w, width, alignInvrt[attributes.widthAlign]); | ||
} | ||
nextMapLocation.y += yCumul; | ||
} else { | ||
yCumul += render[1].height; | ||
} | ||
} | ||
return nextMapLocation; | ||
} | ||
render(attributes: YJoinAttributes, inheritMe: Partial<YJoinAttributes>) { | ||
@@ -73,0 +173,0 @@ const fillChar = new RichChar(attributes.fillChar, attributes).noSize(); |
import { MinitelObject } from '../abstract/minitelobject.js'; | ||
import { LocationDescriptor } from '../locationdescriptor.js'; | ||
import { RichChar } from '../richchar.js'; | ||
import { RichCharGrid } from '../richchargrid.js'; | ||
import { Align, MinitelObjectAttributes } from '../types.js'; | ||
import { getDeltaFromSetting } from '../utils.js'; | ||
import { alignInvrt, inheritedProps } from '../utils.js'; | ||
@@ -31,2 +33,23 @@ import type { Minitel } from './minitel.js'; | ||
} | ||
mapLocation(attributes: ZJoinAttributes, inheritMe: Partial<ZJoinAttributes>, nextNode: MinitelObject, nodes: MinitelObject[], weAt: number): LocationDescriptor { | ||
const renders = this.children.map((v) => [v, v.getDimensionsWrapper(inheritMe, { | ||
width: attributes.width, | ||
height: attributes.height, | ||
})] as const); | ||
const maxWidth = Math.max(...renders.map((v) => v[1].width)); | ||
const maxHeight = Math.max(...renders.map((v) => v[1].height)); | ||
const relevant = renders.find((v) => v[0] === nextNode)!; | ||
const prevLocation = nextNode.mapLocationWrapper(inheritMe, { | ||
width: attributes.width, | ||
height: attributes.height, | ||
}, nodes, weAt); | ||
prevLocation.x += getDeltaFromSetting(relevant[1].width, maxWidth, attributes.widthAlign); | ||
prevLocation.y += getDeltaFromSetting(relevant[1].height, maxWidth, attributes.heightAlign); | ||
return prevLocation; | ||
} | ||
render(attributes: ZJoinAttributes, inheritMe: Partial<ZJoinAttributes>) { | ||
@@ -33,0 +56,0 @@ const fillChar = new RichChar(attributes.fillChar, attributes).noSize(); |
@@ -1,2 +0,2 @@ | ||
import { CharAttributes } from './types.js'; | ||
import { CharAttributes, CharAttributesWithoutDouble } from './types.js'; | ||
@@ -30,3 +30,3 @@ export class RichChar<T> { | ||
const result = []; | ||
const offsets: Record<keyof Omit<CharAttributes, 'doubleHeight' | 'doubleWidth'>, number | number[]> = { | ||
const offsets: Record<keyof CharAttributesWithoutDouble, number | number[]> = { | ||
charset: [0x0f, 0x0e, 0x19], | ||
@@ -39,3 +39,3 @@ noBlink: 0x48, | ||
}; | ||
let attribute: keyof Omit<CharAttributes, 'doubleHeight' | 'doubleWidth'>; | ||
let attribute: keyof CharAttributesWithoutDouble; | ||
for (attribute in offsets) { | ||
@@ -42,0 +42,0 @@ if (attribute in attributes) { |
import { LocationDescriptors } from './locationdescriptor.js'; | ||
import { RichChar } from './richchar.js'; | ||
import { Align, CharAttributes, FullPadding, Padding } from './types.js'; | ||
import { Align, CharAttributes, FullPadding } from './types.js'; | ||
@@ -98,2 +98,30 @@ export class RichCharGrid { | ||
forceIntegrityOn(y: number, x: number) { | ||
const cell = this.grid[y][x]; | ||
if (!cell.delta) return; | ||
const newY = y + cell.delta[0]; | ||
const newX = x + cell.delta[1]; | ||
if (!(newY in this.grid) || !(newX in this.grid[newY])) { | ||
this.grid[y][x] = cell.copy(); | ||
this.grid[y][x].delta = undefined; | ||
this.grid[y][x].actualChar = undefined; | ||
if (x === 0) this.grid[y][x].char = '<'; | ||
else this.grid[y][x].char = '>'; | ||
} | ||
} | ||
forceIntegrityOnTheSides() { | ||
for (let side = 0; side < 2; side += 1) { | ||
for (let y = 0; y < this.height; y += 1) { | ||
const currY = y; | ||
const currX = side * (this.width - 1); | ||
this.forceIntegrityOn(currY, currX); | ||
} | ||
for (let x = 0; x < this.width; x += 1) { | ||
const currX = x; | ||
const currY = side * (this.height - 1); | ||
this.forceIntegrityOn(currY, currX); | ||
} | ||
} | ||
} | ||
cutHeight(height: number, heightAlign: Align) { | ||
@@ -112,2 +140,3 @@ const prevHeight = this.height; | ||
this.locationDescriptors.cut(this.height, this.width); | ||
this.forceIntegrityOnTheSides(); | ||
return this; | ||
@@ -119,2 +148,3 @@ case 'middle': | ||
this.cutHeight(height, 'end'); | ||
this.forceIntegrityOnTheSides(); | ||
return this; | ||
@@ -136,2 +166,3 @@ } | ||
this.locationDescriptors.cut(this.height, this.width); | ||
this.forceIntegrityOnTheSides(); | ||
return this; | ||
@@ -143,2 +174,3 @@ case 'middle': | ||
this.cutWidth(width, 'end'); | ||
this.forceIntegrityOnTheSides(); | ||
return this; | ||
@@ -148,3 +180,3 @@ } | ||
setHeight(height: number, heightAlign: Align, char = new RichChar(' ')) { | ||
setHeight(height: number, heightAlign: Align, char: RichChar<string>) { | ||
if (this.height === height) return this; | ||
@@ -167,3 +199,3 @@ if (this.height > height) return this.cutHeight(height, heightAlign); | ||
} | ||
setWidth(width: number, widthAlign: Align, char = new RichChar(' ')) { | ||
setWidth(width: number, widthAlign: Align, char: RichChar<string>) { | ||
if (this.width === width) return this; | ||
@@ -170,0 +202,0 @@ if (this.width > width) return this.cutWidth(width, widthAlign); |
@@ -17,2 +17,3 @@ // import { InputAttributes } fr``om './components/input.js'; | ||
} | ||
export type CharAttributesWithoutDouble = Omit<CharAttributes, 'doubleHeight' | 'doubleWidth'>; | ||
export type ApplicableCharAttributes = Omit<CharAttributes, 'charset'>; | ||
@@ -19,0 +20,0 @@ export type Align = 'start' | 'middle' | 'end'; |
@@ -10,3 +10,3 @@ import { Align, FullPadding, MinitelObjectAttributes, Padding } from './types.js'; | ||
export function inheritedProps<T extends MinitelObjectAttributes> (props: Partial<T>): Partial<T> { | ||
const inheritedProps = ['fillChar', 'fg', 'textAlign', 'bg', 'underline', 'noBlink', 'invert', 'doubleWidth', 'doubleHeight', 'wrap'] as const; | ||
const inheritedProps = ['fillChar', 'fg', 'textAlign', 'bg', 'underline', 'doubleWidth', 'doubleHeight', 'noBlink', 'invert', 'wrap'] as const; | ||
const result: Partial<T> = {}; | ||
@@ -49,1 +49,8 @@ | ||
} | ||
export function getDeltaFromSetting(size: number, setInto: number, align: Align) { | ||
if (align === 'start') return 0; | ||
if (align === 'end') return setInto - size; | ||
return Math.floor((setInto - size) / 2); | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
258328
75
5494
4