Web: Improve touch support and layout.
This commit is contained in:
parent
b5cde57bb6
commit
4981570844
@ -375,8 +375,8 @@ public:
|
|||||||
double x = 0;
|
double x = 0;
|
||||||
double y = 0;
|
double y = 0;
|
||||||
for (int i = 0; i < emEvent.numTouches; i++) {
|
for (int i = 0; i < emEvent.numTouches; i++) {
|
||||||
x += emEvent.touches[i].clientX;
|
x += emEvent.touches[i].targetX;
|
||||||
y += emEvent.touches[i].clientY;
|
y += emEvent.touches[i].targetY;
|
||||||
}
|
}
|
||||||
dst_x = x / emEvent.numTouches;
|
dst_x = x / emEvent.numTouches;
|
||||||
dst_y = y / emEvent.numTouches;
|
dst_y = y / emEvent.numTouches;
|
||||||
@ -386,10 +386,10 @@ public:
|
|||||||
if (emEvent.numTouches < 2) {
|
if (emEvent.numTouches < 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
double x1 = emEvent.touches[0].clientX;
|
double x1 = emEvent.touches[0].targetX;
|
||||||
double y1 = emEvent.touches[0].clientY;
|
double y1 = emEvent.touches[0].targetY;
|
||||||
double x2 = emEvent.touches[1].clientX;
|
double x2 = emEvent.touches[1].targetX;
|
||||||
double y2 = emEvent.touches[1].clientY;
|
double y2 = emEvent.touches[1].targetY;
|
||||||
dst_distance = std::sqrt(std::pow(x1 - x2, 2) + std::pow(y1 - y2, 2));
|
dst_distance = std::sqrt(std::pow(x1 - x2, 2) + std::pow(y1 - y2, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,10 +574,14 @@ public:
|
|||||||
|
|
||||||
val htmlContainer;
|
val htmlContainer;
|
||||||
val htmlEditor;
|
val htmlEditor;
|
||||||
|
val scrollbarHelper;
|
||||||
|
|
||||||
std::function<void()> editingDoneFunc;
|
std::function<void()> editingDoneFunc;
|
||||||
std::shared_ptr<MenuBarImplHtml> menuBar;
|
std::shared_ptr<MenuBarImplHtml> menuBar;
|
||||||
|
|
||||||
|
bool useWorkaround_devicePixelRatio = false;
|
||||||
|
|
||||||
|
|
||||||
WindowImplHtml(val htmlContainer, std::string emCanvasSel) :
|
WindowImplHtml(val htmlContainer, std::string emCanvasSel) :
|
||||||
emCanvasSel(emCanvasSel),
|
emCanvasSel(emCanvasSel),
|
||||||
htmlContainer(htmlContainer),
|
htmlContainer(htmlContainer),
|
||||||
@ -591,7 +595,34 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
htmlEditor.call<void>("addEventListener", val("trigger"), Wrap(&editingDoneFunc));
|
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(
|
sscheck(emscripten_set_resize_callback(
|
||||||
EMSCRIPTEN_EVENT_TARGET_WINDOW, this, /*useCapture=*/false,
|
EMSCRIPTEN_EVENT_TARGET_WINDOW, this, /*useCapture=*/false,
|
||||||
@ -618,21 +649,18 @@ public:
|
|||||||
emCanvasSel.c_str(), this, /*useCapture=*/false,
|
emCanvasSel.c_str(), this, /*useCapture=*/false,
|
||||||
WindowImplHtml::MouseCallback));
|
WindowImplHtml::MouseCallback));
|
||||||
|
|
||||||
{
|
sscheck(emscripten_set_touchstart_callback(
|
||||||
std::string altCanvasSelector = "#canvas0";
|
emCanvasSel.c_str(), this, /*useCapture=*/false,
|
||||||
sscheck(emscripten_set_touchstart_callback(
|
WindowImplHtml::TouchCallback));
|
||||||
altCanvasSelector.c_str(), this, /*useCapture=*/false,
|
sscheck(emscripten_set_touchmove_callback(
|
||||||
WindowImplHtml::TouchCallback));
|
emCanvasSel.c_str(), this, /*useCapture=*/false,
|
||||||
sscheck(emscripten_set_touchmove_callback(
|
WindowImplHtml::TouchCallback));
|
||||||
altCanvasSelector.c_str(), this, /*useCapture=*/false,
|
sscheck(emscripten_set_touchend_callback(
|
||||||
WindowImplHtml::TouchCallback));
|
emCanvasSel.c_str(), this, /*useCapture=*/false,
|
||||||
sscheck(emscripten_set_touchend_callback(
|
WindowImplHtml::TouchCallback));
|
||||||
altCanvasSelector.c_str(), this, /*useCapture=*/false,
|
sscheck(emscripten_set_touchcancel_callback(
|
||||||
WindowImplHtml::TouchCallback));
|
emCanvasSel.c_str(), this, /*useCapture=*/false,
|
||||||
sscheck(emscripten_set_touchcancel_callback(
|
WindowImplHtml::TouchCallback));
|
||||||
altCanvasSelector.c_str(), this, /*useCapture=*/false,
|
|
||||||
WindowImplHtml::TouchCallback));
|
|
||||||
}
|
|
||||||
|
|
||||||
sscheck(emscripten_set_wheel_callback(
|
sscheck(emscripten_set_wheel_callback(
|
||||||
emCanvasSel.c_str(), this, /*useCapture=*/false,
|
emCanvasSel.c_str(), this, /*useCapture=*/false,
|
||||||
@ -903,14 +931,24 @@ public:
|
|||||||
double width, height;
|
double width, height;
|
||||||
std::string htmlContainerSel = "#" + htmlContainer["id"].as<std::string>();
|
std::string htmlContainerSel = "#" + htmlContainer["id"].as<std::string>();
|
||||||
sscheck(emscripten_get_element_css_size(htmlContainerSel.c_str(), &width, &height));
|
sscheck(emscripten_get_element_css_size(htmlContainerSel.c_str(), &width, &height));
|
||||||
width *= emscripten_get_device_pixel_ratio();
|
// sscheck(emscripten_get_element_css_size(emCanvasSel.c_str(), &width, &height));
|
||||||
height *= emscripten_get_device_pixel_ratio();
|
|
||||||
int curWidth, curHeight;
|
if (this->useWorkaround_devicePixelRatio) {
|
||||||
sscheck(emscripten_get_canvas_element_size(emCanvasSel.c_str(), &curWidth, &curHeight));
|
// Workaround is to skip applying devicePixelRatio.
|
||||||
if(curWidth != (int)width || curHeight != (int)curHeight) {
|
// So NOP here.
|
||||||
dbp("Canvas %s: resizing to (%g,%g)", emCanvasSel.c_str(), width, height);
|
} else {
|
||||||
sscheck(emscripten_set_canvas_element_size(
|
double devicePixelRatio = emscripten_get_device_pixel_ratio();
|
||||||
emCanvasSel.c_str(), (int)width, (int)height));
|
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);
|
std::static_pointer_cast<MenuBarImplHtml>(menuBar);
|
||||||
this->menuBar = menuBarImpl;
|
this->menuBar = menuBarImpl;
|
||||||
|
|
||||||
val htmlBody = val::global("document")["body"];
|
val htmlMain = val::global("document").call<val>("querySelector", val("main"));
|
||||||
val htmlCurrentMenuBar = htmlBody.call<val>("querySelector", val(".menubar"));
|
val htmlCurrentMenuBar = htmlMain.call<val>("querySelector", val(".menubar"));
|
||||||
if(htmlCurrentMenuBar.as<bool>()) {
|
if(htmlCurrentMenuBar.as<bool>()) {
|
||||||
htmlCurrentMenuBar.call<void>("remove");
|
htmlCurrentMenuBar.call<void>("remove");
|
||||||
}
|
}
|
||||||
htmlBody.call<void>("insertBefore", menuBarImpl->htmlMenuBar,
|
htmlMain.call<void>("insertBefore", menuBarImpl->htmlMenuBar,
|
||||||
htmlBody["firstChild"]);
|
htmlMain["firstChild"]);
|
||||||
ResizeCanvasElement();
|
ResizeCanvasElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1026,8 +1064,11 @@ public:
|
|||||||
void ShowEditor(double x, double y, double fontHeight, double minWidth,
|
void ShowEditor(double x, double y, double fontHeight, double minWidth,
|
||||||
bool isMonospace, const std::string &text) override {
|
bool isMonospace, const std::string &text) override {
|
||||||
htmlEditor["style"].set("display", val(""));
|
htmlEditor["style"].set("display", val(""));
|
||||||
htmlEditor["style"].set("left", std::to_string(x - 4) + "px");
|
val canvasClientRect = val::global("document").call<val>("querySelector", val(this->emCanvasSel)).call<val>("getBoundingClientRect");
|
||||||
htmlEditor["style"].set("top", std::to_string(y - fontHeight - 2) + "px");
|
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("fontSize", std::to_string(fontHeight) + "px");
|
||||||
htmlEditor["style"].set("minWidth", std::to_string(minWidth) + "px");
|
htmlEditor["style"].set("minWidth", std::to_string(minWidth) + "px");
|
||||||
htmlEditor["style"].set("fontFamily", isMonospace ? "monospace" : "sans");
|
htmlEditor["style"].set("fontFamily", isMonospace ? "monospace" : "sans");
|
||||||
@ -1040,22 +1081,54 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SetScrollbarVisible(bool visible) override {
|
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 scrollbarPos = 0.0;
|
||||||
|
double scrollbarMin = 0.0;
|
||||||
|
double scrollbarMax = 0.0;
|
||||||
|
double scrollbarPageSize = 0.0;
|
||||||
|
|
||||||
void ConfigureScrollbar(double min, double max, double pageSize) override {
|
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
|
// 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 {
|
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;
|
return scrollbarPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetScrollbarPosition(double pos) override {
|
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;
|
scrollbarPos = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1170,15 +1243,15 @@ public:
|
|||||||
|
|
||||||
Response RunModal() {
|
Response RunModal() {
|
||||||
// ssassert(false, "RunModal not supported on Emscripten");
|
// ssassert(false, "RunModal not supported on Emscripten");
|
||||||
dbp("MessageDialog::RunModal() called.");
|
// dbp("MessageDialog::RunModal() called.");
|
||||||
this->ShowModal();
|
this->ShowModal();
|
||||||
//FIXME(emscripten): use val::await() with JavaScript's Promise
|
//FIXME(emscripten): use val::await() with JavaScript's Promise
|
||||||
while (true) {
|
while (true) {
|
||||||
if (this->is_shown) {
|
if (this->is_shown) {
|
||||||
dbp("MessageDialog::RunModal(): is_shown == true");
|
// dbp("MessageDialog::RunModal(): is_shown == true");
|
||||||
emscripten_sleep(2000);
|
emscripten_sleep(2000);
|
||||||
} else {
|
} else {
|
||||||
dbp("MessageDialog::RunModal(): break due to is_shown == false");
|
// dbp("MessageDialog::RunModal(): break due to is_shown == false");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1187,7 +1260,7 @@ public:
|
|||||||
return this->latestResponse;
|
return this->latestResponse;
|
||||||
} else {
|
} else {
|
||||||
// FIXME(emscripten):
|
// FIXME(emscripten):
|
||||||
dbp("MessageDialog::RunModal(): Cannot get Response.");
|
// dbp("MessageDialog::RunModal(): Cannot get Response.");
|
||||||
return this->latestResponse;
|
return this->latestResponse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1289,7 +1362,7 @@ public:
|
|||||||
bool RunModal() override {
|
bool RunModal() override {
|
||||||
//FIXME(emscripten):
|
//FIXME(emscripten):
|
||||||
dbp("FileOpenDialogImplHtml::RunModal()");
|
dbp("FileOpenDialogImplHtml::RunModal()");
|
||||||
this->filename = "untitled.slvs";
|
this->filename = "/untitled.slvs";
|
||||||
this->fileUploadHelper.call<void>("showDialog");
|
this->fileUploadHelper.call<void>("showDialog");
|
||||||
|
|
||||||
//FIXME(emscripten): use val::await() with JavaScript's Promise
|
//FIXME(emscripten): use val::await() with JavaScript's Promise
|
||||||
@ -1298,7 +1371,7 @@ public:
|
|||||||
while (true) {
|
while (true) {
|
||||||
bool is_shown = this->fileUploadHelper["is_shown"].as<bool>();
|
bool is_shown = this->fileUploadHelper["is_shown"].as<bool>();
|
||||||
if (!is_shown) {
|
if (!is_shown) {
|
||||||
dbp("FileOpenDialogImplHtml: break due to is_shown == false");
|
// dbp("FileOpenDialogImplHtml: break due to is_shown == false");
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
// dbp("FileOpenDialogImplHtml: sleep 100msec... (%d)", is_shown);
|
// dbp("FileOpenDialogImplHtml: sleep 100msec... (%d)", is_shown);
|
||||||
@ -1309,7 +1382,7 @@ public:
|
|||||||
val selectedFilenameVal = this->fileUploadHelper["currentFilename"];
|
val selectedFilenameVal = this->fileUploadHelper["currentFilename"];
|
||||||
|
|
||||||
if (selectedFilenameVal == val::null()) {
|
if (selectedFilenameVal == val::null()) {
|
||||||
dbp("selectedFilenameVal is null");
|
// dbp("selectedFilenameVal is null");
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
std::string selectedFilename = selectedFilenameVal.as<std::string>();
|
std::string selectedFilename = selectedFilenameVal.as<std::string>();
|
||||||
@ -1356,9 +1429,11 @@ public:
|
|||||||
|
|
||||||
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
||||||
this->filters = "";
|
this->filters = "";
|
||||||
for (auto extension : extensions) {
|
for (size_t i = 0; i < extensions.size(); i++) {
|
||||||
this->filters = "." + extension;
|
if (i != 0) {
|
||||||
this->filters += ",";
|
this->filters += ",";
|
||||||
|
}
|
||||||
|
this->filters = "." + extensions[i];
|
||||||
}
|
}
|
||||||
dbp("filter=%s", this->filters.c_str());
|
dbp("filter=%s", this->filters.c_str());
|
||||||
}
|
}
|
||||||
@ -1381,13 +1456,13 @@ public:
|
|||||||
|
|
||||||
FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) {
|
FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) {
|
||||||
// FIXME(emscripten): implement
|
// FIXME(emscripten): implement
|
||||||
dbp("CreateOpenFileDialog()");
|
// dbp("CreateOpenFileDialog()");
|
||||||
return std::shared_ptr<FileOpenDialogImplHtml>(new FileOpenDialogImplHtml());
|
return std::shared_ptr<FileOpenDialogImplHtml>(new FileOpenDialogImplHtml());
|
||||||
}
|
}
|
||||||
|
|
||||||
FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) {
|
FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) {
|
||||||
// FIXME(emscripten): implement
|
// FIXME(emscripten): implement
|
||||||
dbp("CreateSaveFileDialog()");
|
// dbp("CreateSaveFileDialog()");
|
||||||
return std::shared_ptr<FileSaveDummyDialogImplHtml>(new FileSaveDummyDialogImplHtml());
|
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"),
|
val::global("window").call<void>("addEventListener", val("beforeunload"),
|
||||||
Wrap(&onBeforeUnload));
|
Wrap(&onBeforeUnload));
|
||||||
|
|
||||||
dbp("Set onSaveFinished");
|
// dbp("Set onSaveFinished");
|
||||||
SS.OnSaveFinished = OnSaveFinishedCallback;
|
SS.OnSaveFinished = OnSaveFinishedCallback;
|
||||||
|
|
||||||
// FIXME(emscripten): get locale from user preferences
|
// FIXME(emscripten): get locale from user preferences
|
||||||
|
@ -21,13 +21,21 @@
|
|||||||
<progress id="progress" value="0" max="100" hidden="1"></progress>
|
<progress id="progress" value="0" max="100" hidden="1"></progress>
|
||||||
</div>
|
</div>
|
||||||
</div><!--
|
</div><!--
|
||||||
FIXME(emscripten): without this, a window resize is required in Chrome
|
--><main><!--
|
||||||
to get the layout to update and canvas size to match up. What?
|
FIXME(emscripten): without this, a window resize is required in Chrome
|
||||||
--><ul class="menu menubar" style="visibility: hidden"><li>None</li></ul><!--
|
to get the layout to update and canvas size to match up. What?
|
||||||
--><div id="container"><!--
|
--><ul class="menu menubar" style="visibility: hidden"><li>None</li></ul><!--
|
||||||
--><div id="container0"><canvas id="canvas0"></canvas></div><!--
|
--><div id="container"><!--
|
||||||
--><div id="container1"><canvas id="canvas1"></canvas></div><!--
|
--><div id="container0"><canvas id="canvas0"></canvas></div><!--
|
||||||
--></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">
|
--><script type="text/javascript">
|
||||||
var splashElement = document.getElementById('splash');
|
var splashElement = document.getElementById('splash');
|
||||||
var spinnerElement = document.getElementById('spinner');
|
var spinnerElement = document.getElementById('spinner');
|
||||||
|
@ -7,6 +7,7 @@ html, body {
|
|||||||
background: black;
|
background: black;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
html, body, canvas, #splash, #container {
|
html, body, canvas, #splash, #container {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -53,6 +54,16 @@ body {
|
|||||||
to { transform: rotate(360deg); }
|
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 */
|
/* Buttons */
|
||||||
.button {
|
.button {
|
||||||
border: 1px solid hsl(0, 0%, 60%);
|
border: 1px solid hsl(0, 0%, 60%);
|
||||||
@ -70,8 +81,8 @@ body {
|
|||||||
|
|
||||||
/* Editors */
|
/* Editors */
|
||||||
.editor {
|
.editor {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
padding: 1px 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,19 +270,52 @@ canvas {
|
|||||||
|
|
||||||
#container {
|
#container {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
/* FIXME(emscripten): this should be dynamically adjustable, not hardcoded in CSS */
|
/* FIXME(emscripten): this should be dynamically adjustable, not hardcoded in CSS */
|
||||||
#container0 {
|
#container0 {
|
||||||
flex-basis: 80%;
|
flex-basis: 80%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
#container1 {
|
|
||||||
|
#container1parent {
|
||||||
flex-basis: 20%;
|
flex-basis: 20%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
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 hasModal = !!document.querySelector('.modal');
|
||||||
var hasMenuBar = !!document.querySelector('.menubar .selected');
|
var hasMenuBar = !!document.querySelector('.menubar .selected');
|
||||||
var hasPopupMenu = !!document.querySelector('.menu.popup');
|
var hasPopupMenu = !!document.querySelector('.menu.popup');
|
||||||
var hasEditor = false;
|
return hasModal || hasMenuBar || hasPopupMenu;
|
||||||
document.querySelectorAll('.editor').forEach(function(editor) {
|
|
||||||
if(editor.style.display == "") {
|
|
||||||
hasEditor = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return hasModal || hasMenuBar || hasPopupMenu || hasEditor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* String helpers */
|
/* 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 */
|
/* Button helpers */
|
||||||
function isButton(element) {
|
function isButton(element) {
|
||||||
return hasClass(element, 'button');
|
return hasClass(element, 'button');
|
||||||
@ -103,6 +107,15 @@ window.addEventListener('click', function(event) {
|
|||||||
button.dispatchEvent(new Event('trigger'));
|
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) {
|
window.addEventListener('keydown', function(event) {
|
||||||
var selected = document.querySelector('.button.selected');
|
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) {
|
window.addEventListener('mouseover', function(event) {
|
||||||
var menuItem = getMenuItem(event.target);
|
var menuItem = getMenuItem(event.target);
|
||||||
var menu = getMenu(menuItem);
|
var menu = getMenu(menuItem);
|
||||||
@ -643,3 +690,123 @@ function saveFileDone(filename, isSaveAs, isAutosave) {
|
|||||||
fileDownloadHelper.showDialog();
|
fileDownloadHelper.showDialog();
|
||||||
console.log(`shoDialog() finished.`);
|
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
Block a user