Web: Improve touch support and layout.
parent
b5cde57bb6
commit
4981570844
|
@ -375,8 +375,8 @@ public:
|
|||
double x = 0;
|
||||
double y = 0;
|
||||
for (int i = 0; i < emEvent.numTouches; i++) {
|
||||
x += emEvent.touches[i].clientX;
|
||||
y += emEvent.touches[i].clientY;
|
||||
x += emEvent.touches[i].targetX;
|
||||
y += emEvent.touches[i].targetY;
|
||||
}
|
||||
dst_x = x / emEvent.numTouches;
|
||||
dst_y = y / emEvent.numTouches;
|
||||
|
@ -386,10 +386,10 @@ public:
|
|||
if (emEvent.numTouches < 2) {
|
||||
return;
|
||||
}
|
||||
double x1 = emEvent.touches[0].clientX;
|
||||
double y1 = emEvent.touches[0].clientY;
|
||||
double x2 = emEvent.touches[1].clientX;
|
||||
double y2 = emEvent.touches[1].clientY;
|
||||
double x1 = emEvent.touches[0].targetX;
|
||||
double y1 = emEvent.touches[0].targetY;
|
||||
double x2 = emEvent.touches[1].targetX;
|
||||
double y2 = emEvent.touches[1].targetY;
|
||||
dst_distance = std::sqrt(std::pow(x1 - x2, 2) + std::pow(y1 - y2, 2));
|
||||
}
|
||||
|
||||
|
@ -574,10 +574,14 @@ public:
|
|||
|
||||
val htmlContainer;
|
||||
val htmlEditor;
|
||||
val scrollbarHelper;
|
||||
|
||||
std::function<void()> editingDoneFunc;
|
||||
std::shared_ptr<MenuBarImplHtml> menuBar;
|
||||
|
||||
bool useWorkaround_devicePixelRatio = false;
|
||||
|
||||
|
||||
WindowImplHtml(val htmlContainer, std::string emCanvasSel) :
|
||||
emCanvasSel(emCanvasSel),
|
||||
htmlContainer(htmlContainer),
|
||||
|
@ -591,7 +595,34 @@ public:
|
|||
}
|
||||
};
|
||||
htmlEditor.call<void>("addEventListener", val("trigger"), Wrap(&editingDoneFunc));
|
||||
htmlContainer.call<void>("appendChild", htmlEditor);
|
||||
htmlContainer["parentElement"].call<void>("appendChild", htmlEditor);
|
||||
|
||||
std::string scrollbarElementQuery = emCanvasSel + "scrollbar";
|
||||
dbp("scrollbar element query: \"%s\"", scrollbarElementQuery.c_str());
|
||||
val scrollbarElement = val::global("document").call<val>("querySelector", val(scrollbarElementQuery));
|
||||
if (scrollbarElement == val::null()) {
|
||||
// dbp("scrollbar element is null.");
|
||||
this->scrollbarHelper = val::null();
|
||||
} else {
|
||||
dbp("scrollbar element OK.");
|
||||
this->scrollbarHelper = val::global("window")["ScrollbarHelper"].new_(val(scrollbarElementQuery));
|
||||
static std::function<void()> onScrollCallback = [this] {
|
||||
// dbp("onScrollCallback std::function this=%p", (void*)this);
|
||||
if (this->onScrollbarAdjusted) {
|
||||
double newpos = this->scrollbarHelper.call<double>("getScrollbarPosition");
|
||||
// dbp(" call onScrollbarAdjusted(%f)", newpos);
|
||||
this->onScrollbarAdjusted(newpos);
|
||||
}
|
||||
this->Invalidate();
|
||||
};
|
||||
this->scrollbarHelper.set("onScrollCallback", Wrap(&onScrollCallback));
|
||||
}
|
||||
|
||||
//FIXME(emscripten): In Chrome for Android on tablet device, devicePixelRatio should not be multiplied.
|
||||
std::string userAgent = val::global("navigator")["userAgent"].as<std::string>();
|
||||
bool is_smartphone = userAgent.find("Mobile") != std::string::npos;
|
||||
bool is_android_device = userAgent.find("Android") != std::string::npos;
|
||||
this->useWorkaround_devicePixelRatio = is_android_device && !is_smartphone;
|
||||
|
||||
sscheck(emscripten_set_resize_callback(
|
||||
EMSCRIPTEN_EVENT_TARGET_WINDOW, this, /*useCapture=*/false,
|
||||
|
@ -618,21 +649,18 @@ public:
|
|||
emCanvasSel.c_str(), this, /*useCapture=*/false,
|
||||
WindowImplHtml::MouseCallback));
|
||||
|
||||
{
|
||||
std::string altCanvasSelector = "#canvas0";
|
||||
sscheck(emscripten_set_touchstart_callback(
|
||||
altCanvasSelector.c_str(), this, /*useCapture=*/false,
|
||||
emCanvasSel.c_str(), this, /*useCapture=*/false,
|
||||
WindowImplHtml::TouchCallback));
|
||||
sscheck(emscripten_set_touchmove_callback(
|
||||
altCanvasSelector.c_str(), this, /*useCapture=*/false,
|
||||
emCanvasSel.c_str(), this, /*useCapture=*/false,
|
||||
WindowImplHtml::TouchCallback));
|
||||
sscheck(emscripten_set_touchend_callback(
|
||||
altCanvasSelector.c_str(), this, /*useCapture=*/false,
|
||||
emCanvasSel.c_str(), this, /*useCapture=*/false,
|
||||
WindowImplHtml::TouchCallback));
|
||||
sscheck(emscripten_set_touchcancel_callback(
|
||||
altCanvasSelector.c_str(), this, /*useCapture=*/false,
|
||||
emCanvasSel.c_str(), this, /*useCapture=*/false,
|
||||
WindowImplHtml::TouchCallback));
|
||||
}
|
||||
|
||||
sscheck(emscripten_set_wheel_callback(
|
||||
emCanvasSel.c_str(), this, /*useCapture=*/false,
|
||||
|
@ -903,14 +931,24 @@ public:
|
|||
double width, height;
|
||||
std::string htmlContainerSel = "#" + htmlContainer["id"].as<std::string>();
|
||||
sscheck(emscripten_get_element_css_size(htmlContainerSel.c_str(), &width, &height));
|
||||
width *= emscripten_get_device_pixel_ratio();
|
||||
height *= emscripten_get_device_pixel_ratio();
|
||||
int curWidth, curHeight;
|
||||
sscheck(emscripten_get_canvas_element_size(emCanvasSel.c_str(), &curWidth, &curHeight));
|
||||
if(curWidth != (int)width || curHeight != (int)curHeight) {
|
||||
dbp("Canvas %s: resizing to (%g,%g)", emCanvasSel.c_str(), width, height);
|
||||
sscheck(emscripten_set_canvas_element_size(
|
||||
emCanvasSel.c_str(), (int)width, (int)height));
|
||||
// sscheck(emscripten_get_element_css_size(emCanvasSel.c_str(), &width, &height));
|
||||
|
||||
if (this->useWorkaround_devicePixelRatio) {
|
||||
// Workaround is to skip applying devicePixelRatio.
|
||||
// So NOP here.
|
||||
} else {
|
||||
double devicePixelRatio = emscripten_get_device_pixel_ratio();
|
||||
width *= devicePixelRatio;
|
||||
height *= devicePixelRatio;
|
||||
}
|
||||
|
||||
int currentWidth = 0, currentHeight = 0;
|
||||
sscheck(emscripten_get_canvas_element_size(emCanvasSel.c_str(), ¤tWidth, ¤tHeight));
|
||||
|
||||
if ((int)width != currentWidth || (int)height != currentHeight) {
|
||||
// dbp("Canvas %s container current size: (%d, %d)", emCanvasSel.c_str(), (int)currentWidth, (int)currentHeight);
|
||||
// dbp("Canvas %s: resizing to (%d, %d)", emCanvasSel.c_str(), (int)width, (int)height);
|
||||
sscheck(emscripten_set_canvas_element_size(emCanvasSel.c_str(), (int)width, (int)height));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -977,13 +1015,13 @@ public:
|
|||
std::static_pointer_cast<MenuBarImplHtml>(menuBar);
|
||||
this->menuBar = menuBarImpl;
|
||||
|
||||
val htmlBody = val::global("document")["body"];
|
||||
val htmlCurrentMenuBar = htmlBody.call<val>("querySelector", val(".menubar"));
|
||||
val htmlMain = val::global("document").call<val>("querySelector", val("main"));
|
||||
val htmlCurrentMenuBar = htmlMain.call<val>("querySelector", val(".menubar"));
|
||||
if(htmlCurrentMenuBar.as<bool>()) {
|
||||
htmlCurrentMenuBar.call<void>("remove");
|
||||
}
|
||||
htmlBody.call<void>("insertBefore", menuBarImpl->htmlMenuBar,
|
||||
htmlBody["firstChild"]);
|
||||
htmlMain.call<void>("insertBefore", menuBarImpl->htmlMenuBar,
|
||||
htmlMain["firstChild"]);
|
||||
ResizeCanvasElement();
|
||||
}
|
||||
|
||||
|
@ -1026,8 +1064,11 @@ public:
|
|||
void ShowEditor(double x, double y, double fontHeight, double minWidth,
|
||||
bool isMonospace, const std::string &text) override {
|
||||
htmlEditor["style"].set("display", val(""));
|
||||
htmlEditor["style"].set("left", std::to_string(x - 4) + "px");
|
||||
htmlEditor["style"].set("top", std::to_string(y - fontHeight - 2) + "px");
|
||||
val canvasClientRect = val::global("document").call<val>("querySelector", val(this->emCanvasSel)).call<val>("getBoundingClientRect");
|
||||
double canvasLeft = canvasClientRect["left"].as<double>();
|
||||
double canvasTop = canvasClientRect["top"].as<double>();
|
||||
htmlEditor["style"].set("left", std::to_string(canvasLeft + x - 4) + "px");
|
||||
htmlEditor["style"].set("top", std::to_string(canvasTop + y - fontHeight - 2) + "px");
|
||||
htmlEditor["style"].set("fontSize", std::to_string(fontHeight) + "px");
|
||||
htmlEditor["style"].set("minWidth", std::to_string(minWidth) + "px");
|
||||
htmlEditor["style"].set("fontFamily", isMonospace ? "monospace" : "sans");
|
||||
|
@ -1040,22 +1081,54 @@ public:
|
|||
}
|
||||
|
||||
void SetScrollbarVisible(bool visible) override {
|
||||
// FIXME(emscripten): implement
|
||||
// dbp("SetScrollbarVisible(): visible=%d", visible ? 1 : 0);
|
||||
if (this->scrollbarHelper == val::null()) {
|
||||
// dbp("scrollbarHelper is null.");
|
||||
return;
|
||||
}
|
||||
if (!visible) {
|
||||
this->scrollbarHelper.call<void>("setScrollbarEnabled", val(false));
|
||||
}
|
||||
}
|
||||
|
||||
double scrollbarPos = 0.0;
|
||||
double scrollbarMin = 0.0;
|
||||
double scrollbarMax = 0.0;
|
||||
double scrollbarPageSize = 0.0;
|
||||
|
||||
void ConfigureScrollbar(double min, double max, double pageSize) override {
|
||||
// dbp("ConfigureScrollbar(): min=%f, max=%f, pageSize=%f", min, max, pageSize);
|
||||
if (this->scrollbarHelper == val::null()) {
|
||||
// dbp("scrollbarHelper is null.");
|
||||
return;
|
||||
}
|
||||
// FIXME(emscripten): implement
|
||||
this->scrollbarMin = min;
|
||||
this->scrollbarMax = max;
|
||||
this->scrollbarPageSize = pageSize;
|
||||
|
||||
this->scrollbarHelper.call<void>("setRange", this->scrollbarMin, this->scrollbarMax);
|
||||
this->scrollbarHelper.call<void>("setPageSize", pageSize);
|
||||
}
|
||||
|
||||
double GetScrollbarPosition() override {
|
||||
// FIXME(emscripten): implement
|
||||
// dbp("GetScrollbarPosition()");
|
||||
if (this->scrollbarHelper == val::null()) {
|
||||
// dbp("scrollbarHelper is null.");
|
||||
return 0;
|
||||
}
|
||||
this->scrollbarPos = this->scrollbarHelper.call<double>("getScrollbarPosition");
|
||||
// dbp(" GetScrollbarPosition() returns %f", this->scrollbarPos);
|
||||
return scrollbarPos;
|
||||
}
|
||||
|
||||
void SetScrollbarPosition(double pos) override {
|
||||
// FIXME(emscripten): implement
|
||||
// dbp("SetScrollbarPosition(): pos=%f", pos);
|
||||
if (this->scrollbarHelper == val::null()) {
|
||||
// dbp("scrollbarHelper is null.");
|
||||
return;
|
||||
}
|
||||
this->scrollbarHelper.call<void>("setScrollbarPosition", pos);
|
||||
scrollbarPos = pos;
|
||||
}
|
||||
|
||||
|
@ -1170,15 +1243,15 @@ public:
|
|||
|
||||
Response RunModal() {
|
||||
// ssassert(false, "RunModal not supported on Emscripten");
|
||||
dbp("MessageDialog::RunModal() called.");
|
||||
// dbp("MessageDialog::RunModal() called.");
|
||||
this->ShowModal();
|
||||
//FIXME(emscripten): use val::await() with JavaScript's Promise
|
||||
while (true) {
|
||||
if (this->is_shown) {
|
||||
dbp("MessageDialog::RunModal(): is_shown == true");
|
||||
// dbp("MessageDialog::RunModal(): is_shown == true");
|
||||
emscripten_sleep(2000);
|
||||
} else {
|
||||
dbp("MessageDialog::RunModal(): break due to is_shown == false");
|
||||
// dbp("MessageDialog::RunModal(): break due to is_shown == false");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1187,7 +1260,7 @@ public:
|
|||
return this->latestResponse;
|
||||
} else {
|
||||
// FIXME(emscripten):
|
||||
dbp("MessageDialog::RunModal(): Cannot get Response.");
|
||||
// dbp("MessageDialog::RunModal(): Cannot get Response.");
|
||||
return this->latestResponse;
|
||||
}
|
||||
}
|
||||
|
@ -1289,7 +1362,7 @@ public:
|
|||
bool RunModal() override {
|
||||
//FIXME(emscripten):
|
||||
dbp("FileOpenDialogImplHtml::RunModal()");
|
||||
this->filename = "untitled.slvs";
|
||||
this->filename = "/untitled.slvs";
|
||||
this->fileUploadHelper.call<void>("showDialog");
|
||||
|
||||
//FIXME(emscripten): use val::await() with JavaScript's Promise
|
||||
|
@ -1298,7 +1371,7 @@ public:
|
|||
while (true) {
|
||||
bool is_shown = this->fileUploadHelper["is_shown"].as<bool>();
|
||||
if (!is_shown) {
|
||||
dbp("FileOpenDialogImplHtml: break due to is_shown == false");
|
||||
// dbp("FileOpenDialogImplHtml: break due to is_shown == false");
|
||||
break;
|
||||
} else {
|
||||
// dbp("FileOpenDialogImplHtml: sleep 100msec... (%d)", is_shown);
|
||||
|
@ -1309,7 +1382,7 @@ public:
|
|||
val selectedFilenameVal = this->fileUploadHelper["currentFilename"];
|
||||
|
||||
if (selectedFilenameVal == val::null()) {
|
||||
dbp("selectedFilenameVal is null");
|
||||
// dbp("selectedFilenameVal is null");
|
||||
return false;
|
||||
} else {
|
||||
std::string selectedFilename = selectedFilenameVal.as<std::string>();
|
||||
|
@ -1356,10 +1429,12 @@ public:
|
|||
|
||||
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
||||
this->filters = "";
|
||||
for (auto extension : extensions) {
|
||||
this->filters = "." + extension;
|
||||
for (size_t i = 0; i < extensions.size(); i++) {
|
||||
if (i != 0) {
|
||||
this->filters += ",";
|
||||
}
|
||||
this->filters = "." + extensions[i];
|
||||
}
|
||||
dbp("filter=%s", this->filters.c_str());
|
||||
}
|
||||
|
||||
|
@ -1381,13 +1456,13 @@ public:
|
|||
|
||||
FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) {
|
||||
// FIXME(emscripten): implement
|
||||
dbp("CreateOpenFileDialog()");
|
||||
// dbp("CreateOpenFileDialog()");
|
||||
return std::shared_ptr<FileOpenDialogImplHtml>(new FileOpenDialogImplHtml());
|
||||
}
|
||||
|
||||
FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) {
|
||||
// FIXME(emscripten): implement
|
||||
dbp("CreateSaveFileDialog()");
|
||||
// dbp("CreateSaveFileDialog()");
|
||||
return std::shared_ptr<FileSaveDummyDialogImplHtml>(new FileSaveDummyDialogImplHtml());
|
||||
}
|
||||
|
||||
|
@ -1415,7 +1490,7 @@ std::vector<std::string> InitGui(int argc, char **argv) {
|
|||
val::global("window").call<void>("addEventListener", val("beforeunload"),
|
||||
Wrap(&onBeforeUnload));
|
||||
|
||||
dbp("Set onSaveFinished");
|
||||
// dbp("Set onSaveFinished");
|
||||
SS.OnSaveFinished = OnSaveFinishedCallback;
|
||||
|
||||
// FIXME(emscripten): get locale from user preferences
|
||||
|
|
|
@ -21,13 +21,21 @@
|
|||
<progress id="progress" value="0" max="100" hidden="1"></progress>
|
||||
</div>
|
||||
</div><!--
|
||||
--><main><!--
|
||||
FIXME(emscripten): without this, a window resize is required in Chrome
|
||||
to get the layout to update and canvas size to match up. What?
|
||||
--><ul class="menu menubar" style="visibility: hidden"><li>None</li></ul><!--
|
||||
--><div id="container"><!--
|
||||
--><div id="container0"><canvas id="canvas0"></canvas></div><!--
|
||||
--><div id="view_separator"></div><!--
|
||||
--><div id="container1parent"><!--
|
||||
--><div id="container1"><canvas id="canvas1"></canvas></div><!--
|
||||
--><div id="canvas1scrollbarbox"><!--
|
||||
--><div id="canvas1scrollbar"></div><!--
|
||||
--></div><!--
|
||||
--></div><!--
|
||||
--></div><!--
|
||||
--></main><!--
|
||||
--><script type="text/javascript">
|
||||
var splashElement = document.getElementById('splash');
|
||||
var spinnerElement = document.getElementById('spinner');
|
||||
|
|
|
@ -7,6 +7,7 @@ html, body {
|
|||
background: black;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
html, body, canvas, #splash, #container {
|
||||
margin: 0;
|
||||
|
@ -53,6 +54,16 @@ body {
|
|||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Grid layout for main */
|
||||
main {
|
||||
height: 100%;
|
||||
|
||||
/* Use CSS Grid layout for vertical placement. */
|
||||
display: grid;
|
||||
/* Row 0 for menubar (fit to content), Row 1 for canvas0, canvas1 (rest of space) */
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.button {
|
||||
border: 1px solid hsl(0, 0%, 60%);
|
||||
|
@ -70,8 +81,8 @@ body {
|
|||
|
||||
/* Editors */
|
||||
.editor {
|
||||
position: absolute;
|
||||
padding: 1px 0;
|
||||
position: fixed;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
@ -259,19 +270,52 @@ canvas {
|
|||
|
||||
#container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* FIXME(emscripten): this should be dynamically adjustable, not hardcoded in CSS */
|
||||
#container0 {
|
||||
flex-basis: 80%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
#container1 {
|
||||
|
||||
#container1parent {
|
||||
flex-basis: 20%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-width: 410px;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto 19px;
|
||||
grid-template-rows: 100%;
|
||||
}
|
||||
#canvas1 {
|
||||
min-width: 400px;
|
||||
|
||||
#container1 {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#canvas1scrollbarbox {
|
||||
/* 19px is a magic number for scrollbar width (Yes, this is platform-dependent value but looks almost working.) */
|
||||
width: 19px;
|
||||
min-width: 19px;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
|
||||
background-color: lightgray;
|
||||
-webkit-overflow-scrolling: auto;
|
||||
}
|
||||
|
||||
#canvas1scrollbar {
|
||||
/* 0px will disable the scrollbar by browser. */
|
||||
width: 1px;
|
||||
/* Disable scrollbar as default. This value will be overwritten by program. */
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#view_separator {
|
||||
width: 4px;
|
||||
background: hsl(0, 0%, 20%);
|
||||
}
|
||||
|
|
|
@ -2,13 +2,7 @@ function isModal() {
|
|||
var hasModal = !!document.querySelector('.modal');
|
||||
var hasMenuBar = !!document.querySelector('.menubar .selected');
|
||||
var hasPopupMenu = !!document.querySelector('.menu.popup');
|
||||
var hasEditor = false;
|
||||
document.querySelectorAll('.editor').forEach(function(editor) {
|
||||
if(editor.style.display == "") {
|
||||
hasEditor = true;
|
||||
}
|
||||
});
|
||||
return hasModal || hasMenuBar || hasPopupMenu || hasEditor;
|
||||
return hasModal || hasMenuBar || hasPopupMenu;
|
||||
}
|
||||
|
||||
/* String helpers */
|
||||
|
@ -78,6 +72,16 @@ function setLabelWithMnemonic(element, labelText) {
|
|||
}
|
||||
}
|
||||
|
||||
/** Touchevent helper
|
||||
* @param {TouchEvent} event
|
||||
* @return {boolean} true if same element is target of touchstart and touchend
|
||||
*/
|
||||
function isSameElementOnTouchstartAndTouchend(event) {
|
||||
const elementOnTouchStart = event.target;
|
||||
const elementOnTouchEnd = document.elementFromPoint(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
|
||||
return elementOnTouchStart == elementOnTouchEnd;
|
||||
}
|
||||
|
||||
/* Button helpers */
|
||||
function isButton(element) {
|
||||
return hasClass(element, 'button');
|
||||
|
@ -103,6 +107,15 @@ window.addEventListener('click', function(event) {
|
|||
button.dispatchEvent(new Event('trigger'));
|
||||
}
|
||||
});
|
||||
window.addEventListener("touchend", (event) => {
|
||||
if (!isSameElementOnTouchstartAndTouchend(event)) {
|
||||
return;
|
||||
}
|
||||
const button = getButton(event.target);
|
||||
if (button) {
|
||||
button.dispatchEvent(new Event('trigger'));
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('keydown', function(event) {
|
||||
var selected = document.querySelector('.button.selected');
|
||||
|
@ -267,6 +280,40 @@ window.addEventListener('click', function(event) {
|
|||
});
|
||||
}
|
||||
});
|
||||
window.addEventListener("touchend", (event) => {
|
||||
if (!isSameElementOnTouchstartAndTouchend(event)) {
|
||||
return;
|
||||
}
|
||||
var menuItem = getMenuItem(event.target);
|
||||
var menu = getMenu(menuItem);
|
||||
if(menu && isMenubar(menu)) {
|
||||
if(hasClass(menuItem, 'selected')) {
|
||||
removeClass(menuItem, 'selected');
|
||||
} else {
|
||||
selectMenuItem(menuItem);
|
||||
}
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
} else if(menu) {
|
||||
if(!hasSubmenu(menuItem)) {
|
||||
triggerMenuItem(menuItem);
|
||||
} else {
|
||||
addClass(menuItem, "selected");
|
||||
addClass(menuItem, "hover");
|
||||
}
|
||||
event.stopPropagation();
|
||||
} else {
|
||||
document.querySelectorAll('.menu .selected, .menu .hover')
|
||||
.forEach(function(menuItem) {
|
||||
deselectMenuItem(menuItem);
|
||||
event.stopPropagation();
|
||||
});
|
||||
document.querySelectorAll('.menu.popup')
|
||||
.forEach(function(menu) {
|
||||
menu.remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
window.addEventListener('mouseover', function(event) {
|
||||
var menuItem = getMenuItem(event.target);
|
||||
var menu = getMenu(menuItem);
|
||||
|
@ -643,3 +690,123 @@ function saveFileDone(filename, isSaveAs, isAutosave) {
|
|||
fileDownloadHelper.showDialog();
|
||||
console.log(`shoDialog() finished.`);
|
||||
}
|
||||
|
||||
|
||||
class ScrollbarHelper {
|
||||
/**
|
||||
* @param {HTMLElement} elementquery CSS query string for the element that has scrollbar.
|
||||
*/
|
||||
constructor(elementquery) {
|
||||
this.target = document.querySelector(elementquery);
|
||||
this.rangeMin = 0;
|
||||
this.rangeMax = 0;
|
||||
this.currentRatio = 0;
|
||||
|
||||
this.onScrollCallback = null;
|
||||
this.onScrollCallbackTicking = false;
|
||||
if (this.target) {
|
||||
// console.log("addEventListner scroll");
|
||||
this.target.parentElement.addEventListener('scroll', () => {
|
||||
if (this.onScrollCallbackTicking) {
|
||||
return;
|
||||
}
|
||||
window.requestAnimationFrame(() => {
|
||||
if (this.onScrollCallback) {
|
||||
this.onScrollCallback();
|
||||
}
|
||||
this.onScrollCallbackTicking = false;
|
||||
});
|
||||
this.onScrollCallbackTicking = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} ratio how long against to the viewport height (1.0 to exact same as viewport's height)
|
||||
*/
|
||||
setScrollbarSize(ratio) {
|
||||
// if (isNaN(ratio)) {
|
||||
// console.warn(`setScrollbarSize(): ratio is Nan = ${ratio}`);
|
||||
// }
|
||||
// if (ratio < 0 || ratio > 1) {
|
||||
// console.warn(`setScrollbarSize(): ratio is out of range 0-1 but ${ratio}`);
|
||||
// }
|
||||
// console.log(`ScrollbarHelper.setScrollbarSize(): ratio=${ratio}`);
|
||||
this.target.style.height = `${100 * ratio}%`;
|
||||
}
|
||||
|
||||
getScrollbarPosition() {
|
||||
const scrollbarElem = this.target.parentElement;
|
||||
const scrollTopMin = 0;
|
||||
const scrollTopMax = scrollbarElem.scrollHeight - scrollbarElem.clientHeight;
|
||||
const ratioOnScrollbar = (scrollbarElem.scrollTop - scrollTopMin) / (scrollTopMax - scrollTopMin);
|
||||
this.currentRatio = (scrollbarElem.scrollTop - scrollTopMin) / (scrollTopMax - scrollTopMin);
|
||||
let pos = this.currentRatio * (this.rangeMax - this.pageSize - this.rangeMin) + this.rangeMin;
|
||||
// console.log(`ScrollbarHelper.getScrollbarPosition(): ratio=${ratioOnScrollbar}, pos=${pos}, scrollTop=${scrollbarElem.scrollTop}, scrollTopMin=${scrollTopMin}, scrollTopMax=${scrollTopMax}, rangeMin=${this.rangeMin}, rangeMax=${this.rangeMax}, pageSize=${this.pageSize}`);
|
||||
if (isNaN(pos)) {
|
||||
return 0;
|
||||
} else {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value in range of rangeMin and rangeMax
|
||||
*/
|
||||
setScrollbarPosition(position) {
|
||||
const positionMin = this.rangeMin;
|
||||
const positionMax = this.rangeMax - this.pageSize;
|
||||
const currentPositionRatio = (position - positionMin) / (positionMax - positionMin);
|
||||
|
||||
const scrollbarElement = this.target.parentElement;
|
||||
const scrollTopMin = 0;
|
||||
const scrollTopMax = scrollbarElement.scrollHeight - scrollbarElement.clientHeight;
|
||||
const scrollWidth = scrollTopMax - scrollTopMin;
|
||||
const newScrollTop = currentPositionRatio * scrollWidth;
|
||||
scrollbarElement.scrollTop = currentPositionRatio * scrollWidth;
|
||||
|
||||
// console.log(`ScrollbarHelper.setScrollbarPosition(): pos=${position}, currentPositionRatio=${currentPositionRatio}, calculated scrollTop=${newScrollTop}`);
|
||||
|
||||
if (false) {
|
||||
// const ratio = (position - this.rangeMin) * ((this.rangeMax - this.pageSize) - this.rangeMin);
|
||||
|
||||
const scrollTopMin = 0;
|
||||
const scrollTopMax = this.target.scrollHeight - this.target.clientHeight;
|
||||
const scrollWidth = scrollTopMax - scrollTopMin;
|
||||
const newScrollTop = ratio * scrollWidth;
|
||||
// this.target.parentElement.scrollTop = ratio * scrollWidth;
|
||||
this.target.scrollTop = ratio * scrollWidth;
|
||||
|
||||
console.log(`ScrollbarHelper.setScrollbarPosition(): pos=${position}, ratio=${ratio}, calculated scrollTop=${newScrollTop}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** */
|
||||
setRange(min, max, pageSize) {
|
||||
this.rangeMin = min;
|
||||
this.rangeMax = max;
|
||||
this.currentRatio = 0;
|
||||
|
||||
this.setPageSize(pageSize);
|
||||
}
|
||||
|
||||
setPageSize(pageSize) {
|
||||
if (this.rangeMin == this.rangeMax) {
|
||||
// console.log(`ScrollbarHelper::setPageSize(): size=${size}, but rangeMin == rangeMax`);
|
||||
return;
|
||||
}
|
||||
this.pageSize = pageSize;
|
||||
const ratio = (this.rangeMax - this.rangeMin) / this.pageSize;
|
||||
// console.log(`ScrollbarHelper::setPageSize(): pageSize=${pageSize}, ratio=${ratio}`);
|
||||
this.setScrollbarSize(ratio);
|
||||
}
|
||||
|
||||
setScrollbarEnabled(enabled) {
|
||||
if (!enabled) {
|
||||
this.target.style.height = "100%";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.ScrollbarHelper = ScrollbarHelper;
|
||||
|
|
Loading…
Reference in New Issue