UNPKG

suranadira-keyboard

Version:

A component for displaying Suranadira texts.

406 lines (346 loc) 10.6 kB
/** * Suranadira Keyboard * * Copyright 2019, Armands Strazds <armands@strazds.com> * */ import React, { useState, useEffect } from "react"; import suranadiraCore from "../../suranadira-core"; import Debug, { info } from "suranadira-utils/debug"; import { getClickXY } from "suranadira-utils/dom"; import { encode } from "suranadira-utils/arrays"; import { useKeyDown } from "suranadira-utils/keyboard"; import { removeUID } from "suranadira-utils/string"; import { createPath, getPathIndex } from "suranadira-utils/canvas"; import { updateComposition } from "./keyboard-layout"; const strokes = suranadiraCore.suranadiraStrokes; const elements = suranadiraCore.suranadiraElements; const { getWordID } = suranadiraCore.suranadiraWords; // Control commands const showKeyNames = false; // The Keyboard app const SuranadiraKeyboard = args => { // Defaults let defaults = { width: window.innerWidth, maxWidth: 900, height: 80, color: "#000", colorPressed: "#ff0000", background: "#ddd", keyWidth: 7, keyHeight: 7, onClickAllowed: true, onKeyDownAllowed: true, onComposition: () => {}, onLayoutSet: () => {}, layoutDefault: new Set(["Z", "J"]), unit: 20, lineWidth: 5, charSpaceX: 6, charSpaceY: 3, focusRectPaddingX: -2, focusRectPaddingY: 0, locked: false, visible: true, resize: "", styles: {} }; // Properties let properties = Object.assign(defaults, args); if (typeof args.styles !== "undefined") { properties = Object.assign(properties, args.styles); } properties.height = properties.unit * 4; // Limit component's width if (properties.width > properties.maxWidth) properties.width = properties.maxWidth; // Create canvas reference let refCanvas = React.createRef(); // External hooks let keyDown = useKeyDown(); // Define state hooks const [position, setPosition] = useState(-1); // as in "positional number system" const [composition, setComposition] = useState([]); const [layout, setLayout] = useState(properties.layoutDefault); const [paths, setPaths] = useState(null); // Set the default layout useEffect(() => { drawLayout(properties.layoutDefault); setLayout(properties.layoutDefault); // eslint-disable-next-line }, []); // Dispatcher for externale commands useEffect(() => { if (!properties.action) return; // Send out the script switch (removeUID(properties.action)) { // On Composition done case "DONE": Debug( info({ module: "SuranadiraKeyboard.js", func: "useEffect [properties.action]", param: "composition", value: composition }) ); properties.onDone(composition); resetKeyboard(); break; // On Composition cancelled case "CANCEL": properties.onCancel(); resetKeyboard(); break; // On Message sent case "SENT": resetKeyboard(); break; default: // ignore break; } // eslint-disable-next-line }, [properties.action]); // Map keyDown to virtual keyDown useEffect(() => { if (properties.locked) return; // Unserialze the keyDown return let keyID = removeUID(keyDown); // Show the keyID in console if (showKeyNames) console.log(keyID); // Map a physical numeric key to virtual keyboard if (!isNaN(parseFloat(keyID)) && isFinite(keyID)) { onKeyDown(keyID); } // Finalize Composition else if (keyID === "Enter") { properties.onDone(composition); resetKeyboard(); } // Cancel Composition else if (keyID === "Escape") { properties.onCancel(); resetKeyboard(); } // eslint-disable-next-line }, [keyDown]); // Handle the device dimensions change useEffect(() => { Debug( info({ module: "SuranadiraKeyboard.js", func: "useEffect [properties.resize]", param: "layout", value: layout }) ); drawLayout(layout); // eslint-disable-next-line }, [properties.resize]); // Fire event onLayoutSet useEffect(() => { let diff = new Set( [...layout].filter(x => !properties.layoutDefault.has(x)) ); properties.onLayoutSet(layout, layout.size !== 0 && diff.size === 0); // eslint-disable-next-line }, [layout]); // Map mouseClick to virtual keyDown const onClick = e => { if (!properties.onClickAllowed) return; let keyID = getKeyIDByMouseEvent(e); // Trigger key down, if a key was pressed if (keyID !== false) onKeyDown(keyID); }; const getKeyIDByMouseEvent = e => { // Get CTX let canvas = refCanvas.current; let ctx = canvas.getContext("2d"); // Get point coordinates let point = getClickXY(e); let x = point.x; let y = point.y; // Get path index let keyID = getPathIndex({ ctx, paths, x, y }); return keyID; }; const onMouseDown = e => { let keyID = getKeyIDByMouseEvent(e); console.log("onMouseDown keyID", keyID); drawElement(keyID, properties.colorPressed); }; const onMouseUp = e => { let keyID = getKeyIDByMouseEvent(e); console.log("onMouseUp keyID", keyID); drawElement(keyID, properties.color); }; // Send the virtual keyDown to Callback const onKeyDown = keyID => { if (!properties.onKeyDownAllowed) return; // Compose character according to the key command composeCharacter(keyID); }; // Set Suranadirakeyboard layout const drawLayout = lo => { if (typeof lo === "undefined") return; // Clear keyboard clearKeyboard(); let _paths = [], _path; // Draw Suranadira Elements for (let keyID = 0; keyID < lo.size; keyID++) { _path = drawElement(keyID, properties.color, lo); _paths.push(_path); } setPaths(_paths); setLayout(lo); }; // Draw an Element / a Keyboard Key const drawElement = (keyID, color, lo) => { if (typeof lo === "undefined") lo = layout; if (typeof color === "undefined") color = properties.color; let ctx = getCTX(); if (!ctx) return; var _lo = Array.from(lo); let element = _lo[keyID]; let marginLeft = 2; let offset = keyID * properties.keyWidth; // Stroke properties strokes.properties.ctx = ctx; strokes.properties.unit = properties.unit; strokes.properties.lineWidth = properties.lineWidth; strokes.properties.left = 0; strokes.properties.top = properties.unit * 1.5; strokes.properties.strokeStyle = color; let x, shape; try { for (let stroke of elements[element].strokes) { shape = stroke[0]; x = stroke[1]; strokes.draw(shape, x + offset + marginLeft, 0); } } catch (e) { // ignore } // Set path properties let pathProperties = { left: (offset + marginLeft) * strokes.properties.unit - properties.focusRectPaddingX, top: strokes.properties.top - properties.focusRectPaddingY, width: strokes.properties.unit * properties.charSpaceX + 2 * properties.focusRectPaddingX, height: strokes.properties.unit * properties.charSpaceY + -2 * properties.focusRectPaddingY }; // Create path for events for the composition let path = createPath({ ctx: ctx, properties: pathProperties, invisible: true }); return path; }; // Compose character const composeCharacter = keyID => { let _layout = layout; let _position = position; let ret = updateComposition( keyID, // numeric key 0..n for choice selection position, // position within the positional number system layout, // a set of element choices composition // current composition of the character ); // Send composition and character ID to callback if (ret !== false) { var [co, cp] = ret; // Draw layout if (typeof co !== undefined) { drawLayout(co); _layout = co; } // Save state setComposition(cp); _position = position + 1; // Callback composition if (cp.length < 4) { properties.onComposition(encode(cp), null); } // Callback composition and character ID else { Debug( info({ module: "SuranadiraKeyboard.js", func: "composeCharacter", param: "cp for getWordID(cp)", value: cp }) ); let ch = getWordID(cp); if (ch !== false) { properties.onComposition(encode(cp), ch); clearKeyboard(); // setPosition(-1); _position = -1; _layout = new Set(); } } } setPosition(_position); setLayout(new Set(_layout)); }; // Get canvas context const getCTX = () => { let canvas = refCanvas.current; let ctx = canvas.getContext("2d"); return ctx; }; // Clear the keyboard canvas const clearKeyboard = () => { // let canvas = refCanvas.current; // let ctx = canvas.getContext("2d"); let ctx = getCTX(); if (!ctx) return; ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); return ctx; }; // Reset keyboard after composition is done const resetKeyboard = () => { drawLayout(properties.layoutDefault); setPosition(-1); setComposition([]); }; return ( <div className="SuranadiraKeyboard" style={{ background: "none", // properties.background, position: "relative", display: properties.visible ? "inline-block" : "none", width: properties.width, height: properties.height, // boxSizing: "border-box", overflow: "hidden" }} > <canvas ref={refCanvas} style={{ background: properties.background, // "none", position: "relative", display: "block" }} width={properties.width + "px"} height={properties.height + "px"} onClick={onClick} onMouseDown={onMouseDown} onMouseUp={onMouseUp} /> </div> ); }; export default SuranadiraKeyboard;