/** * Toolbar */ const toolbar = { _locked: false, _toolbar: document.getElementById("ui-toolbar"), _toolbar_lock_indicator: document.getElementById("toolbar-lock-indicator"), tools: [], _current_tool: null, get currentTool() { return this._current_tool; }, lock() { toolbar._locked = true; toolbar._toolbar_lock_indicator.style.display = "block"; }, unlock() { toolbar._locked = false; toolbar._toolbar_lock_indicator.style.display = "none"; }, _makeToolbarEntry: (tool) => { const toolTitle = document.createElement("img"); toolTitle.classList.add("tool-icon"); toolTitle.src = tool.icon; const toolEl = document.createElement("div"); toolEl.id = `tool-${tool.id}`; toolEl.classList.add("tool"); toolEl.title = tool.name; if (tool.options.shortcut) toolEl.title += ` (${tool.options.shortcut})`; toolEl.onclick = () => tool.enable(); toolEl.appendChild(toolTitle); return toolEl; }, registerTool( icon, toolname, enable, disable, options = { /** * Runs on tool creation. It receives the tool state. * * Can be used to setup callback functions, for example. */ init: null, /** * Function to populate the state menu. * * It receives a div element (that is the menu) and the current tool state. */ populateContextMenu: null, /** * Help description of the tool; for now used for nothing */ description: "", /** * Shortcut; Text describing this tool's shortcut access */ shortcut: "", } ) { // Set some defaults if (!options.init) options.init = (state) => console.debug(`Initialized tool '${toolname}'`); if (!options.populateContextMenu) options.populateContextMenu = (menu, state) => { const span = document.createElement("span"); span.textContent = "Tool has no context menu"; menu.appendChild(span); return; }; // Create tool const id = guid(); const contextMenuEl = document.getElementById("tool-context"); const tool = { id, icon, name: toolname, enabled: false, _element: null, state: {}, options, /** * If the tool has a redraw() function in its state, then run it */ redraw: () => { tool.state.redraw && tool.state.redraw(); }, enable: (opt = null) => { if (toolbar._locked) return; this.tools.filter((t) => t.enabled).forEach((t) => t.disable()); while (contextMenuEl.lastChild) { contextMenuEl.removeChild(contextMenuEl.lastChild); } options.populateContextMenu(contextMenuEl, tool.state); tool._element && tool._element.classList.add("using"); tool.enabled = true; this._current_tool = tool; enable(tool.state, opt); }, disable: (opt = null) => { tool._element && tool._element.classList.remove("using"); this._current_tool = null; tool.enabled = false; disable(tool.state, opt); }, }; // Initalize options.init && options.init(tool.state); this.tools.push(tool); // Add tool to toolbar tool._element = this._makeToolbarEntry(tool); this._toolbar.appendChild(tool._element); return tool; }, addSeparator() { const separator = document.createElement("div"); separator.classList.add("separator"); this._toolbar.appendChild(separator); }, }; /** * Premade inputs for populating the context menus */ const _toolbar_input = { checkbox: (state, dataKey, text, cb = null) => { if (state[dataKey] === undefined) state[dataKey] = false; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.checked = state[dataKey]; checkbox.onchange = () => { state[dataKey] = checkbox.checked; cb && cb(); }; const label = document.createElement("label"); label.appendChild(checkbox); label.appendChild(new Text(text)); return {checkbox, label}; }, slider: (state, dataKey, text, options = {}) => { defaultOpt(options, {min: 0, max: 1, step: 0.1, textStep: null, cb: null}); const slider = document.createElement("div"); const value = createSlider(text, slider, { min: options.min, max: options.max, step: options.step, valuecb: (v) => { state[dataKey] = v; options.cb && options.cb(v); }, defaultValue: state[dataKey], textStep: options.textStep, }); return { slider, setValue(v) { value.value = v; return value.value; }, }; }, };