VulpesOS/gaia/apps/keyboard/js/views/layout_page_view.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

364 lines
10 KiB
JavaScript
Executable file

'use strict';
/* global HandwritingPadView, KeyView, KeyboardEvent,
LatinCandidatePanelView, CandidatePanelView */
(function(exports) {
/**
* Each keyboard layout may have multiple pages, one for default and another
* for symbol input, etc.
* LayoutPageView represents each page in the keyboard.
*/
function LayoutPageView(layout, options, viewManager) {
this.layout = layout;
this.options = options;
this.viewManager = viewManager;
this.isUpperCase = undefined;
this.rows = new Map();
// Each row would contain the following info:
//{ element: , // DOM element for this row
// keys: , // A map to store all the keys.
//}
this.candidatePanel = null;
// Cache the visual data here
this.keyArrays = new Map();
}
LayoutPageView.prototype.render = function render() {
var layout = this.layout;
var content = document.createDocumentFragment();
var container = document.createElement('div');
if (this.options.classNames) {
container.classList.add.apply(container.classList, this.options.classNames);
}
if (layout.specificCssRule) {
container.classList.add(layout.layoutName);
}
// Render candidate panel
if (this.options.showCandidatePanel) {
var candidatePanel = this.createCandidatePanel(this.layout.imEngine);
candidatePanel.render();
container.classList.add('candidate-panel');
container.appendChild(candidatePanel.element);
this.candidatePanel = candidatePanel;
}
// Create canvas for handwriting.
if ('handwritingPadOptions' in layout) {
var target = {
isHandwritingPad: true
};
var handwritingPadView = new HandwritingPadView(target,
null,
this.viewManager);
handwritingPadView.render();
content.appendChild(handwritingPadView.element);
this.handwritingPadView = handwritingPadView;
}
container.classList.add('rows-' + layout.keys.length);
if (layout.keys.length !== 4 &&
layout.keys.length !== 5) {
console.warn('LayoutPageView: Layout to render is neither 4 or 5 rows; ' +
'might not be render with expected height.', layout.keys.length);
}
layout.keys.forEach((function buildKeyboardRow(row, nrow) {
var kbRow = document.createElement('div');
var rowLayoutWidth = 0;
kbRow.classList.add('keyboard-row');
kbRow.classList.add('row' + nrow);
if (nrow === layout.keys.length - 1) {
kbRow.classList.add('keyboard-last-row');
}
var keyCount = 0;
var keyMap = new Map();
// Calculate the layout width for each row first.
row.forEach(function calcRowRatio(key, keyIndex) {
var ratio = key.ratio || 1;
rowLayoutWidth += ratio;
});
if ('handwritingPadOptions' in layout &&
nrow < layout.handwritingPadOptions.rowspan) {
rowLayoutWidth += layout.handwritingPadOptions.ratio;
}
row.forEach((function buildKeyboardColumns(key, keyIndex) {
var ratio = key.ratio || 1;
// One key in layout may be used to create multiple keyViews in
// different pages, so create a unique instance here.
var target = Object.freeze(Object.create(key));
var options = {
classNames: [],
outputChar: key.uppercaseValue,
outerRatio: ratio,
innerRatio: ratio
};
if (layout.keyClassName) {
options.classNames = options.classNames.concat(
layout.keyClassName.split(' '));
}
var layoutWidth = layout.width || 10;
// Adjust the width of the first and the last key if there are less keys
// in this row.
if (layoutWidth != rowLayoutWidth &&
(keyIndex === 0 || keyIndex === row.length - 1)) {
options.outerRatio = ratio + ((layoutWidth - rowLayoutWidth) / 2);
}
options.altOutputChar = key.value;
var keyView = new KeyView(target, options, this.viewManager);
keyView.render();
kbRow.appendChild(keyView.element);
keyMap.set(keyCount, keyView);
keyCount++;
}.bind(this)));
this.rows.set(nrow, {
element: kbRow,
keys: keyMap
});
content.appendChild(kbRow);
}).bind(this));
container.appendChild(content);
container.setAttribute('lang', layout.lang);
this.element = container;
};
// Accepts a state object with two properties.
// Set isUpperCaseLocked to true if locked
// Set isUpperCase to true when uppercase is enabled
// Use false on both of these properties when uppercase is disabled
LayoutPageView.prototype.setUpperCaseLock = function setUpperCaseLock(state) {
this.isUpperCase = (state.isUpperCase || state.isUpperCaseLocked);
// Toggle the entire container to trigger different rendering
// for upper case state.
var container = this.element;
container.classList.toggle('lowercase', !this.isUpperCase);
//XXX: this should be changed to accessing the KeyView directly.
var capsLockKey = container.querySelector(
'button:not([disabled])' +
'[data-keycode="' + KeyboardEvent.DOM_VK_CAPS_LOCK + '"]'
);
if (!capsLockKey) {
return;
}
if (state.isUpperCaseLocked) {
capsLockKey.classList.remove('kbr-key-active');
capsLockKey.classList.add('kbr-key-hold');
} else if (state.isUpperCase) {
capsLockKey.classList.add('kbr-key-active');
capsLockKey.classList.remove('kbr-key-hold');
} else {
capsLockKey.classList.remove('kbr-key-active');
capsLockKey.classList.remove('kbr-key-hold');
}
capsLockKey.setAttribute('aria-pressed',
state.isUpperCaseLocked || state.isUpperCase);
};
LayoutPageView.prototype.hide = function hide() {
delete this.element.dataset.active;
};
LayoutPageView.prototype.show = function show() {
// For automated testing to locate the active pageView
this.element.dataset.active = true;
};
LayoutPageView.prototype.highlightKey = function highlightKey(target) {
var keyView = this.viewManager.getView(target);
keyView.highlight({upperCase: this.isUpperCase});
};
LayoutPageView.prototype.unHighlightKey = function unHighlightKey(target) {
var keyView = this.viewManager.getView(target);
keyView.unHighlight();
};
LayoutPageView.prototype.resize = function resize(totalWidth) {
this.options.totalWidth = totalWidth;
// Set width and height for handwriting pad.
if (this.handwritingPadView) {
var placeHolderWidth = totalWidth / (this.layout.width || 10);
var width = Math.floor(placeHolderWidth *
this.layout.handwritingPadOptions.ratio);
// Get row height
var height = this.rows.get(0).element.clientHeight *
this.layout.handwritingPadOptions.rowspan;
this.handwritingPadView.resize(width, height);
}
};
LayoutPageView.prototype.getVisualData = function getVisualData() {
var totalWidth = this.options.totalWidth;
var keyArray = this.keyArrays.get(totalWidth);
if (keyArray) {
return keyArray;
}
keyArray = [];
// Now that key sizes have been set and adjusted for the row,
// loop again and record the size and position of each. If we
// do this as part of the loop above, we get bad position data.
// We do this in a seperate loop to avoid reflowing
this.rows.forEach(function (row) {
row.keys.forEach(function(keyView) {
var visualKey = keyView.element.querySelector('.visual-wrapper');
keyArray.push({
code: keyView.target.keyCode,
x: visualKey.offsetLeft,
y: visualKey.offsetTop,
width: visualKey.clientWidth,
height: visualKey.clientHeight
});
});
});
this.keyArrays.set(totalWidth, keyArray);
return keyArray;
};
// A factory method to create different instances of candidate panel.
// Right now, it depends on the input method name to show different types
// of candidate panels.
LayoutPageView.prototype.createCandidatePanel = function(inputMethodName) {
var candidatePanel;
var target = {};
var options = {
className: inputMethodName,
totalWidth: this.options.totalWidth
};
switch (inputMethodName) {
case 'latin':
candidatePanel =
new LatinCandidatePanelView(target, options, this.viewManager);
break;
case 'vietnamese':
options.widthUnit = 1;
candidatePanel =
new CandidatePanelView(target, options, this.viewManager);
break;
default:
candidatePanel =
new CandidatePanelView(target, options, this.viewManager);
break;
}
return candidatePanel;
};
LayoutPageView.prototype.resetCandidatePanel = function() {
if (!this.candidatePanel) {
return;
}
this.candidatePanel.resetScroll();
};
LayoutPageView.prototype.showCandidates = function(candidates) {
if (!this.candidatePanel) {
return;
}
this.candidatePanel.showCandidates(candidates);
};
LayoutPageView.prototype.showMoreCandidates = function(rowLimit, candidates) {
if (!this.candidatePanel) {
return;
}
this.candidatePanel.showMoreCandidates(rowLimit, candidates);
};
LayoutPageView.prototype.getNumberOfCandidatesPerRow = function() {
if (!this.candidatePanel) {
return;
}
return this.candidatePanel.COUNT_PER_ROW;
};
LayoutPageView.prototype.getHeight = function() {
var height = this.rows.size * this.getKeyHeight();
if (this.candidatePanel) {
height += this.candidatePanel.getHeight();
}
return height;
};
LayoutPageView.prototype.getKeyHeight = function() {
// Start with top & bottom margin height
var keyHeightInRem = 0.8;
/**
* There are 4 different heights we are using here.
* See CSS variable '--key-height' in keyboard.css.
*/
if (this.viewManager.screenInPortraitMode()) {
if (this.layout.keys.length === 5) {
keyHeightInRem += 3.4;
} else {
keyHeightInRem += 4.5;
}
} else {
if (this.layout.keys.length === 5) {
keyHeightInRem += 2.85;
} else {
keyHeightInRem += 3.8;
}
}
return keyHeightInRem * this.viewManager.getRemToPx();
};
LayoutPageView.prototype.destroy = function() {
if (this.element) {
// Remove DOM from the tree it belongs to, then main document retain
// no reference to it. Unreachable object will be garbage collected.
this.element.remove();
}
};
exports.LayoutPageView = LayoutPageView;
})(window);