diff --git a/src/fontra/client/core/glyph-controller.js b/src/fontra/client/core/glyph-controller.js index 166bf4beb..32555c1ca 100644 --- a/src/fontra/client/core/glyph-controller.js +++ b/src/fontra/client/core/glyph-controller.js @@ -46,6 +46,8 @@ import { } from "./var-model.js"; import { VarPackedPath, joinPaths } from "./var-path.js"; +export const BACKGROUND_LAYER_SEPARATOR = "^"; + export class VariableGlyphController { constructor(glyph, fontAxes, fontSources) { this.glyph = glyph; @@ -507,7 +509,7 @@ export class VariableGlyphController { const source = this.sources[sourceIndex]; this._layerNameToSourceIndex[source.layerName] = sourceIndex; - const layerNamePrefix = source.layerName + "."; + const layerNamePrefix = source.layerName + BACKGROUND_LAYER_SEPARATOR; const layerNames = Object.keys(this.glyph.layers).filter( (layerName) => layerName.startsWith(layerNamePrefix) && diff --git a/src/fontra/client/lang/de.js b/src/fontra/client/lang/de.js index ffe982baa..4303f3826 100644 --- a/src/fontra/client/lang/de.js +++ b/src/fontra/client/lang/de.js @@ -291,6 +291,7 @@ export const strings = { "sidebar.designspace-navigation.glyph-axes": "Glyph-Achsen", "sidebar.designspace-navigation.glyph-axes.edit": "Glyph-Achsen bearbeiten", "sidebar.designspace-navigation.glyph-axes.reset": "Glyph-Achsen zurücksetzen", + "sidebar.designspace-navigation.glyph-source-layers": "Source layers", "sidebar.designspace-navigation.glyph-sources": "Glyph-Sourcen", "sidebar.designspace-navigation.glyph-sources.name": "Source Name", "sidebar.designspace-navigation.glyph-sources.status": "Status", diff --git a/src/fontra/client/lang/en.js b/src/fontra/client/lang/en.js index 099afe589..58d37bf1a 100644 --- a/src/fontra/client/lang/en.js +++ b/src/fontra/client/lang/en.js @@ -284,6 +284,7 @@ export const strings = { "sidebar.designspace-navigation.glyph-axes": "Glyph axes", "sidebar.designspace-navigation.glyph-axes.edit": "Edit glyph axes", "sidebar.designspace-navigation.glyph-axes.reset": "Reset glyph axes", + "sidebar.designspace-navigation.glyph-source-layers": "Source layers", "sidebar.designspace-navigation.glyph-sources": "Glyph sources", "sidebar.designspace-navigation.glyph-sources.name": "source name", "sidebar.designspace-navigation.glyph-sources.status": "status", diff --git a/src/fontra/client/lang/fr.js b/src/fontra/client/lang/fr.js index df68ca107..29cc1e6f5 100644 --- a/src/fontra/client/lang/fr.js +++ b/src/fontra/client/lang/fr.js @@ -295,6 +295,7 @@ export const strings = { "sidebar.designspace-navigation.glyph-axes": "Axes du glyphe", "sidebar.designspace-navigation.glyph-axes.edit": "Éditer les axes du glyphe", "sidebar.designspace-navigation.glyph-axes.reset": "Réinitialiser les axes du glyphe", + "sidebar.designspace-navigation.glyph-source-layers": "Source layers", "sidebar.designspace-navigation.glyph-sources": "Sources du glyphe", "sidebar.designspace-navigation.glyph-sources.name": "nom de la source", "sidebar.designspace-navigation.glyph-sources.status": "statut", diff --git a/src/fontra/client/lang/ja.js b/src/fontra/client/lang/ja.js index c54c5f7c9..ed8bbb065 100644 --- a/src/fontra/client/lang/ja.js +++ b/src/fontra/client/lang/ja.js @@ -288,6 +288,7 @@ export const strings = { "sidebar.designspace-navigation.glyph-axes": "グリフの補完軸", "sidebar.designspace-navigation.glyph-axes.edit": "グリフの補完軸を編集", "sidebar.designspace-navigation.glyph-axes.reset": "グリフの補完軸をリセット", + "sidebar.designspace-navigation.glyph-source-layers": "Source layers", "sidebar.designspace-navigation.glyph-sources": "グリフソース", "sidebar.designspace-navigation.glyph-sources.name": "ソース名", "sidebar.designspace-navigation.glyph-sources.status": "ステータス", diff --git a/src/fontra/client/lang/nl.js b/src/fontra/client/lang/nl.js index dc97dcbc9..152ba03a9 100644 --- a/src/fontra/client/lang/nl.js +++ b/src/fontra/client/lang/nl.js @@ -288,6 +288,7 @@ export const strings = { "sidebar.designspace-navigation.glyph-axes": "Glyph assen", "sidebar.designspace-navigation.glyph-axes.edit": "Wijzig glyph assen", "sidebar.designspace-navigation.glyph-axes.reset": "Reset glyph assen", + "sidebar.designspace-navigation.glyph-source-layers": "Source layers", "sidebar.designspace-navigation.glyph-sources": "Glyph sources", "sidebar.designspace-navigation.glyph-sources.name": "sourcenaam", "sidebar.designspace-navigation.glyph-sources.status": "status", diff --git a/src/fontra/client/lang/zh-CN.js b/src/fontra/client/lang/zh-CN.js index b357cc151..20fefb101 100644 --- a/src/fontra/client/lang/zh-CN.js +++ b/src/fontra/client/lang/zh-CN.js @@ -273,6 +273,7 @@ export const strings = { "sidebar.designspace-navigation.glyph-axes": "字形参数轴", "sidebar.designspace-navigation.glyph-axes.edit": "编辑字形参数轴", "sidebar.designspace-navigation.glyph-axes.reset": "重置字形参数轴", + "sidebar.designspace-navigation.glyph-source-layers": "Source layers", "sidebar.designspace-navigation.glyph-sources": "字形源", "sidebar.designspace-navigation.glyph-sources.name": "源名称", "sidebar.designspace-navigation.glyph-sources.status": "状态", diff --git a/src/fontra/views/editor/editor.js b/src/fontra/views/editor/editor.js index d1e97dd13..d0bdfc604 100644 --- a/src/fontra/views/editor/editor.js +++ b/src/fontra/views/editor/editor.js @@ -1392,8 +1392,8 @@ export class EditorController extends ViewController { { actionIdentifier: "action.select-next-glyph" }, { actionIdentifier: "action.select-previous-source" }, { actionIdentifier: "action.select-next-source" }, - // { actionIdentifier: "action.select-previous-source-layer" }, - // { actionIdentifier: "action.select-next-source-layer" }, + { actionIdentifier: "action.select-previous-source-layer" }, + { actionIdentifier: "action.select-next-source-layer" }, ], }); diff --git a/src/fontra/views/editor/panel-designspace-navigation.js b/src/fontra/views/editor/panel-designspace-navigation.js index c059ca22f..f887ebb17 100644 --- a/src/fontra/views/editor/panel-designspace-navigation.js +++ b/src/fontra/views/editor/panel-designspace-navigation.js @@ -1,15 +1,15 @@ import { registerAction } from "/core/actions.js"; -import { getAxisBaseName } from "/core/glyph-controller.js"; +import { BACKGROUND_LAYER_SEPARATOR, getAxisBaseName } from "/core/glyph-controller.js"; import * as html from "/core/html-utils.js"; import { htmlToElement } from "/core/html-utils.js"; import { translate } from "/core/localization.js"; -import { controllerKey, ObservableController } from "/core/observable-object.js"; +import { ObservableController, controllerKey } from "/core/observable-object.js"; import { labeledTextInput } from "/core/ui-utils.js"; import { + FocusKeeper, boolInt, enumerate, escapeHTMLCharacters, - FocusKeeper, modulo, objectsEqual, range, @@ -18,7 +18,7 @@ import { scheduleCalls, throttleCalls, } from "/core/utils.js"; -import { GlyphSource, Layer } from "/core/var-glyph.js"; +import { GlyphSource, Layer, StaticGlyph } from "/core/var-glyph.js"; import { isLocationAtDefault, locationToString, @@ -192,7 +192,7 @@ export default class DesignspaceNavigationPanel extends Panel { }, { id: "glyph-layers-accordion-item", - label: "Source layers", // XXXX TODO add translate strings + label: translate("sidebar.designspace-navigation.glyph-source-layers"), open: true, content: html.div( { @@ -202,7 +202,7 @@ export default class DesignspaceNavigationPanel extends Panel { html.createDomElement("ui-list", { id: "layers-list" }), html.createDomElement("add-remove-buttons", { style: "padding: 0.5em 0 0 0;", - id: "glyph-layers-add-remove-buttons", + id: "source-layers-add-remove-buttons", }), ] ), @@ -387,10 +387,16 @@ export default class DesignspaceNavigationPanel extends Panel { this.addRemoveSourceButtons = this.accordion.querySelector( "#sources-list-add-remove-buttons" ); - this.addRemoveSourceButtons.addButtonCallback = () => this.addSource(); this.addRemoveSourceButtons.removeButtonCallback = () => this.removeSource(); + this.addRemoveSourceLayerButtons = this.accordion.querySelector( + "#source-layers-add-remove-buttons" + ); + this.addRemoveSourceLayerButtons.addButtonCallback = () => this.addSourceLayer(); + this.addRemoveSourceLayerButtons.removeButtonCallback = () => + this.removeSourceLayer(); + this.sourcesList.addEventListener("listSelectionChanged", async (event) => { this.sceneController.scrollAdjustBehavior = "pin-glyph-center"; const selectedItem = this.sourcesList.getSelectedItem(); @@ -835,10 +841,6 @@ export default class DesignspaceNavigationPanel extends Panel { } async _updateSourceLayersList() { - // TODO: the background layers feature is not yet functional, disable for now - this.glyphLayersAccordionItem.hidden = true; - return; - const sourceIndex = this.sceneModel.sceneSettings.selectedSourceIndex; const haveLayers = this.sceneModel.selectedGlyph?.isEditing && sourceIndex != undefined; @@ -1314,6 +1316,69 @@ export default class DesignspaceNavigationPanel extends Panel { return { contentElement, warningElement }; } + async addSourceLayer() { + const glyphController = await this.sceneModel.getSelectedVariableGlyphController(); + const glyph = glyphController.glyph; + + const selectedSourceItem = this.sourcesList.getSelectedItem(); + if (!selectedSourceItem) { + return; + } + + const dialog = await dialogSetup("Add layer for source", null, [ + { title: translate("dialog.cancel"), isCancelButton: true }, + { title: translate("dialog.okay"), isDefaultButton: true, result: "ok" }, + ]); + + const nameController = new ObservableController({}); + const contentElement = html.div( + { + style: `overflow: hidden; + white-space: nowrap; + display: grid; + gap: 0.5em; + grid-template-columns: max-content auto; + align-items: center; + `, + }, + labeledTextInput("name", nameController, "sourceLayerName", { + id: "source-layer-name-text-input", + }) + ); + dialog.setContent(contentElement); + + setTimeout( + () => contentElement.querySelector("#source-layer-name-text-input")?.focus(), + 0 + ); + + const result = await dialog.run(); + if (!result) { + return; + } + + const newLayerName = `${selectedSourceItem.layerName}${{ + BACKGROUND_LAYER_SEPARATOR, + }}${nameController.model.sourceLayerName}`; + if (glyph.layers[newLayerName]) { + console.log("layer already exists"); + return; + } + + const newLayer = Layer.fromObject({ + glyph: StaticGlyph.fromObject({ xAdvance: 488 }), + }); + + await this.sceneController.editGlyphAndRecordChanges((glyph) => { + glyph.layers[newLayerName] = newLayer; + return "add source layer"; + }); + } + + removeSourceLayer() { + console.log("remove source layer"); + } + async editGlyphAxes() { const varGlyphController = await this.sceneModel.getSelectedVariableGlyphController(); diff --git a/src/fontra/views/editor/scene-controller.js b/src/fontra/views/editor/scene-controller.js index 659c14106..c9b7e4dd1 100644 --- a/src/fontra/views/editor/scene-controller.js +++ b/src/fontra/views/editor/scene-controller.js @@ -1401,14 +1401,18 @@ export class SceneController { const layerGlyphs = this.getEditingLayerFromGlyphLayers(varGlyph.layers); const staticGlyphControllers = {}; + for (const [i, source] of enumerate(varGlyph.sources)) { - if (source.layerName in layerGlyphs) { - staticGlyphControllers[source.layerName] = - await this.fontController.getLayerGlyphController( - varGlyph.name, - source.layerName, - i - ); + for (const layerInfo of varGlyph.getSourceLayerNamesForSourceIndex(i)) { + const layerName = layerInfo.fullName; + if (layerName in layerGlyphs) { + staticGlyphControllers[layerName] = + await this.fontController.getLayerGlyphController( + varGlyph.name, + layerName, + i + ); + } } } return staticGlyphControllers; diff --git a/src/fontra/views/editor/visualization-layer-definitions.js b/src/fontra/views/editor/visualization-layer-definitions.js index 6414b2696..93a7266ca 100644 --- a/src/fontra/views/editor/visualization-layer-definitions.js +++ b/src/fontra/views/editor/visualization-layer-definitions.js @@ -1411,6 +1411,9 @@ registerVisualizationLayerDefinition({ for (const pointIndex of pointSelection || []) { const pt = glyph.path.getPoint(pointIndex); + if (!pt) { + continue; + } const xString = `${pointIndex}`; const width = context.measureText(xString).width + 2 * margin; context.fillStyle = parameters.boxColor;