Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UI for "Location base" in the source properties UI #1662

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/fontra/client/core/ui-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,39 @@ export function labeledTextInput(label, controller, key, options) {
return items;
}

export function popUpMenu(controller, key, menuItems, options) {
const popUpID = options?.id || `pop-up-${uniqueID()}-${key}`;

const selectElement = html.select(
{
id: popUpID,
onchange: (event) => {
controller.model[key] = event.target.value;
},
},
menuItems.map((menuItem) =>
html.option({ value: menuItem.identifier }, [menuItem.value])
)
);
selectElement.value = controller.model[key];

controller.addKeyListener(key, (event) => {
selectElement.value = event.newValue;
});

if (options?.class) {
selectElement.className = options.class;
}

return selectElement;
}

export function labeledPopUpMenu(label, controller, key, menuItems, options) {
const popUpMenuElement = popUpMenu(controller, key, menuItems, options);
const items = [labelForElement(label, popUpMenuElement), popUpMenuElement];
return items;
}

export const DefaultFormatter = {
toString: (value) => (value !== undefined && value !== null ? value.toString() : ""),
fromString: (value) => {
Expand Down
8 changes: 8 additions & 0 deletions src/fontra/client/core/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,14 @@ export async function mapObjectValuesAsync(obj, func) {
return result;
}

export function filterObject(obj, func) {
// Return a copy of the object containing the items for which `func(key, value)`
// returns `true`.
return Object.fromEntries(
Object.entries(obj).filter(([key, value]) => func(key, value))
);
}

let _uniqueID = 1;
export function uniqueID() {
return _uniqueID++;
Expand Down
100 changes: 88 additions & 12 deletions src/fontra/views/editor/panel-designspace-navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ 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 { labeledTextInput } from "/core/ui-utils.js";
import { labeledPopUpMenu, labeledTextInput } from "/core/ui-utils.js";
import {
boolInt,
enumerate,
escapeHTMLCharacters,
filterObject,
objectsEqual,
range,
rgbaToCSS,
Expand Down Expand Up @@ -812,23 +813,29 @@ export default class DesignspaceNavigationPanel extends Panel {
...this.sceneSettings.glyphLocation,
});

const suggestedLocationBase = undefined; // take from font source, if any

const {
location: newLocation,
sourceName,
layerName,
layerNames,
locationBase,
} = await this._sourcePropertiesRunDialog(
"Add source",
"Add",
glyph,
"",
"",
location
location,
suggestedLocationBase
);
if (!newLocation) {
return;
}

const filteredLocation = stripLocation(newLocation, locationBase, glyph);

const getGlyphFunc = this.sceneController.sceneModel.fontController.getGlyph.bind(
this.sceneController.sceneModel.fontController
);
Expand All @@ -844,7 +851,8 @@ export default class DesignspaceNavigationPanel extends Panel {
GlyphSource.fromObject({
name: sourceName,
layerName: layerName,
location: newLocation,
location: filteredLocation,
locationBase: locationBase,
})
);
if (layerNames.indexOf(layerName) < 0) {
Expand All @@ -869,26 +877,33 @@ export default class DesignspaceNavigationPanel extends Panel {
sourceName,
layerName,
layerNames,
locationBase,
} = await this._sourcePropertiesRunDialog(
"Source properties",
"Done",
glyph,
source.name,
source.layerName,
source.location
source.location,
source.locationBase
);
if (!newLocation) {
return;
}

const filteredLocation = stripLocation(newLocation, locationBase, glyph);

await this.sceneController.editGlyphAndRecordChanges((glyph) => {
const source = glyph.sources[sourceIndex];
if (!objectsEqual(source.location, newLocation)) {
source.location = newLocation;
if (!objectsEqual(source.location, filteredLocation)) {
source.location = filteredLocation;
}
if (sourceName !== source.name) {
source.name = sourceName;
}

source.locationBase = locationBase;

const oldLayerName = source.layerName;
if (layerName !== oldLayerName) {
source.layerName = layerName;
Expand All @@ -915,7 +930,8 @@ export default class DesignspaceNavigationPanel extends Panel {
glyph,
sourceName,
layerName,
location
location,
locationBase
) {
const validateInput = () => {
const warnings = [];
Expand Down Expand Up @@ -951,6 +967,7 @@ export default class DesignspaceNavigationPanel extends Panel {
layerName: layerName === sourceName ? "" : layerName,
suggestedSourceName: suggestedSourceName,
suggestedLayerName: sourceName || suggestedSourceName,
locationBase: locationBase || "",
});

nameController.addKeyListener("sourceName", (event) => {
Expand All @@ -959,7 +976,36 @@ export default class DesignspaceNavigationPanel extends Panel {
validateInput();
});

const glyphAxisNames = getGlyphAxisNamesSet(glyph);

nameController.addKeyListener("locationBase", (event) => {
if (!event.newValue) {
return;
}
const fontSource = this.fontController.sources[event.newValue];
const sourceLocation = fontSource.location;
const fontLocation = filterObject(
sourceLocation,
(name, value) => !glyphAxisNames.has(name)
);
const glyphLocation = filterObject(locationController.model, (name, value) =>
glyphAxisNames.has(name)
);
const newLocation = {
...this.fontController.fontSourcesInstancer.defaultLocation,
...sourceLocation,
...glyphLocation,
};
for (const [name, value] of Object.entries(newLocation)) {
locationController.setItem(name, value, { sentByLocationBase: true });
}
nameController.model.sourceName = fontSource.name;
});

locationController.addListener((event) => {
if (!event.senderInfo?.sentByLocationBase) {
nameController.model.locationBase = "";
}
const suggestedSourceName = suggestedSourceNameFromLocation(
makeSparseLocation(locationController.model, locationAxes)
);
Expand All @@ -986,12 +1032,22 @@ export default class DesignspaceNavigationPanel extends Panel {
);
}

const fontSourceMenuItems = [
{ identifier: "", value: "None" },
...Object.entries(this.fontController.sources).map(
([sourceIdentifier, source]) => {
return { identifier: sourceIdentifier, value: source.name };
}
),
];

const { contentElement, warningElement } = this._sourcePropertiesContentElement(
locationAxes,
nameController,
locationController,
layerNames,
sourceLocations
sourceLocations,
fontSourceMenuItems
);

const dialog = await dialogSetup(title, null, [
Expand All @@ -1018,15 +1074,16 @@ export default class DesignspaceNavigationPanel extends Panel {
nameController.model.sourceName || nameController.model.suggestedSourceName;
layerName =
nameController.model.layerName || nameController.model.suggestedLayerName;
locationBase = nameController.model.locationBase || null;

return { location: newLocation, sourceName, layerName, layerNames };
return { location: newLocation, sourceName, layerName, layerNames, locationBase };
}

_sourcePropertiesLocationAxes(glyph) {
const glyphAxisNames = glyph.axes.map((axis) => axis.name);
const glyphAxisNames = getGlyphAxisNamesSet(glyph);
const fontAxes = mapAxesFromUserSpaceToSourceSpace(
// Don't include font axes that also exist as glyph axes
this.fontController.fontAxes.filter((axis) => !glyphAxisNames.includes(axis.name))
this.fontController.fontAxes.filter((axis) => !glyphAxisNames.has(axis.name))
);
return [
...fontAxes,
Expand All @@ -1040,7 +1097,8 @@ export default class DesignspaceNavigationPanel extends Panel {
nameController,
locationController,
layerNames,
sourceLocations
sourceLocations,
fontSourceMenuItems
) {
const locationElement = html.createDomElement("designspace-location", {
style: `grid-column: 1 / -1;
Expand All @@ -1055,6 +1113,7 @@ export default class DesignspaceNavigationPanel extends Panel {
});
locationElement.axes = locationAxes;
locationElement.controller = locationController;

const contentElement = html.div(
{
style: `overflow: hidden;
Expand All @@ -1068,6 +1127,12 @@ export default class DesignspaceNavigationPanel extends Panel {
`,
},
[
...labeledPopUpMenu(
"Location Base:",
nameController,
"locationBase",
fontSourceMenuItems
),
...labeledTextInput("Source name:", nameController, "sourceName", {
placeholderKey: "suggestedSourceName",
id: "source-name-text-input",
Expand Down Expand Up @@ -1262,6 +1327,17 @@ function suggestedSourceNameFromLocation(location) {
);
}

function getGlyphAxisNamesSet(glyph) {
return new Set(glyph.axes.map((axis) => axis.name));
}

function stripLocation(location, locationBase, glyph) {
const glyphAxisNames = getGlyphAxisNamesSet(glyph);
return locationBase
? filterObject(location, (name, value) => !glyphAxisNames.has(name))
: location;
}

function makeIconCellFactory(
iconPaths,
triggerOnDoubleClick = false,
Expand Down