7 Commits

Author SHA1 Message Date
676fd54982 Landscape mode on Android PWA 2026-05-13 13:52:00 +02:00
084607cc86 Merge remote-tracking branch 'origin/main' into web-changes-android
Merged main repo changes
2026-05-13 13:22:21 +02:00
bbd81665a6 Fix multitouch on web 2026-05-12 17:40:10 +02:00
06398e98e2 Made progressive web app installable 2026-05-12 17:09:27 +02:00
7d9328b865 Adaptation to be able to play on android 2026-05-12 14:59:04 +02:00
f1111d06c2 Scroll on settings 2026-05-12 14:32:05 +02:00
ba9f1da758 Simple http server web 2026-05-12 14:23:26 +02:00
28 changed files with 479 additions and 87 deletions

View File

@@ -339,7 +339,11 @@ if(${PLATFORM} MATCHES "Web")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EM_FLAGS}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EM_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EM_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EM_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${EM_FLAGS} --preload-file ${CMAKE_SOURCE_DIR}/data@/data") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${EM_FLAGS} --preload-file \"${CMAKE_SOURCE_DIR}/data@/data\"")
set_target_properties(${PROJECT_NAME} PROPERTIES RULE_LAUNCH_LINK "set EMCC_WASM_OPT=0 &&")
# Remove -g (DWARF) to prevent wasm-opt post-link optimizations from running
string(REPLACE "-g" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
string(REPLACE "-g" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
target_compile_options(${PROJECT_NAME} PUBLIC target_compile_options(${PROJECT_NAME} PUBLIC
"-Os" "-Os"
@@ -355,7 +359,7 @@ if(${PLATFORM} MATCHES "Web")
"-sFORCE_FILESYSTEM=1" "-sFORCE_FILESYSTEM=1"
"-sLEGACY_GL_EMULATION=1" "-sLEGACY_GL_EMULATION=1"
"-sGL_UNSAFE_OPTS=0" "-sGL_UNSAFE_OPTS=0"
"-sEMULATE_FUNCTION_POINTER_CASTS=1" "-sEMULATE_FUNCTION_POINTER_CASTS=0"
"-sALLOW_TABLE_GROWTH=1" "-sALLOW_TABLE_GROWTH=1"
"-sEXPORTED_RUNTIME_METHODS=['FS','stringToUTF8','UTF8ToString','cwrap','ccall','HEAP8','HEAPU8','HEAP32','HEAPU32']" "-sEXPORTED_RUNTIME_METHODS=['FS','stringToUTF8','UTF8ToString','cwrap','ccall','HEAP8','HEAPU8','HEAP32','HEAPU32']"
"-sEXPORTED_FUNCTIONS=['_main']" "-sEXPORTED_FUNCTIONS=['_main']"
@@ -404,6 +408,10 @@ else()
TARGET ${PROJECT_NAME} TARGET ${PROJECT_NAME}
POST_BUILD POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/misc/web/index.html" $<TARGET_FILE_DIR:${PROJECT_NAME}> COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/misc/web/index.html" $<TARGET_FILE_DIR:${PROJECT_NAME}>
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/misc/web/coi-serviceworker.js" $<TARGET_FILE_DIR:${PROJECT_NAME}>
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/misc/web/manifest.json" $<TARGET_FILE_DIR:${PROJECT_NAME}>
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/misc/web/icon-192.png" $<TARGET_FILE_DIR:${PROJECT_NAME}>
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/misc/web/icon-512.png" $<TARGET_FILE_DIR:${PROJECT_NAME}>
) )
endif() endif()

View File

@@ -176,7 +176,7 @@ cmake --build .
2. Configure and build project: 2. Configure and build project:
``` ```
mkdir build && cd build mkdir build && cd build
cmake .. -B . -G Ninja "-DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="$env:EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake"
cmake --build . --target MinecraftPE cmake --build . --target MinecraftPE
``` ```
> [!Note] > [!Note]

View File

@@ -207,6 +207,7 @@ options.graphics.fancy=Fancy
options.graphics.fast=Fast options.graphics.fast=Fast
options.guiScale=GUI Scale options.guiScale=GUI Scale
options.guiScale.auto=Auto options.guiScale.auto=Auto
options.guiScale.tiny=Tiny
options.guiScale.small=Small options.guiScale.small=Small
options.guiScale.medium=Medium options.guiScale.medium=Medium
options.guiScale.large=Large options.guiScale.large=Large
@@ -225,6 +226,7 @@ options.smoothCamera=Smooth camera
options.destroyVibration=Destroy vibration options.destroyVibration=Destroy vibration
options.isLeftHanded=Left handed options.isLeftHanded=Left handed
options.useTouchscreen=Use touchscreen options.useTouchscreen=Use touchscreen
options.touchOverride=Touch Mode Override
options.fancyGraphics=Fancy graphics options.fancyGraphics=Fancy graphics
options.renderDebug=Debug render options.renderDebug=Debug render
options.anaglyph3d=3D anaglyph options.anaglyph3d=3D anaglyph

View File

@@ -0,0 +1,26 @@
/* coi-serviceworker.js - Cross-Origin Isolation + PWA support */
// Service Worker context
self.addEventListener("install", () => self.skipWaiting());
self.addEventListener("activate", (event) =>
event.waitUntil(self.clients.claim())
);
self.addEventListener("fetch", (event) => {
if (event.request.method !== "GET") return;
event.respondWith(
fetch(event.request).then((response) => {
if (response.status === 0 || response.type === "opaque") return response;
const headers = new Headers(response.headers);
headers.set("Cross-Origin-Embedder-Policy", "require-corp");
headers.set("Cross-Origin-Opener-Policy", "same-origin");
headers.set("Cross-Origin-Resource-Policy", "cross-origin");
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers,
});
}).catch(() => fetch(event.request))
);
});

BIN
misc/web/icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
misc/web/icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -2,6 +2,12 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#000000">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="apple-touch-icon" href="icon-192.png">
<title>MCPE 0.6.1</title> <title>MCPE 0.6.1</title>
<style> <style>
html, body { html, body {
@@ -16,6 +22,10 @@
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
display: block; display: block;
touch-action: none;
user-select: none;
-webkit-user-select: none;
-webkit-tap-highlight-color: transparent;
} }
</style> </style>
</head> </head>
@@ -23,6 +33,40 @@
<canvas id="canvas"></canvas> <canvas id="canvas"></canvas>
<script>
if ('serviceWorker' in navigator) {
var reloadedForSw = false;
navigator.serviceWorker.register('./coi-serviceworker.js', { scope: './' }).then(function () {
navigator.serviceWorker.addEventListener('controllerchange', function () {
if (reloadedForSw) return;
reloadedForSw = true;
window.location.reload();
});
}).catch(function (error) {
console.warn('Service worker registration failed:', error);
});
}
</script>
<script>
function isAndroid() {
return /Android/i.test(navigator.userAgent || '');
}
function lockLandscapeOnAndroid() {
if (!isAndroid()) return;
var orientation = screen.orientation;
if (!orientation || typeof orientation.lock !== 'function') return;
orientation.lock('landscape').catch(function () {});
}
lockLandscapeOnAndroid();
window.addEventListener('pointerdown', lockLandscapeOnAndroid, { once: true });
window.addEventListener('touchstart', lockLandscapeOnAndroid, { once: true, passive: true });
</script>
<script> <script>
var Module = { var Module = {
canvas: document.getElementById('canvas'), canvas: document.getElementById('canvas'),
@@ -43,4 +87,4 @@
<script src="MinecraftPE.js"></script> <script src="MinecraftPE.js"></script>
</body> </body>
</html> </html>

25
misc/web/manifest.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "MCPE 0.6.1",
"short_name": "MCPE 0.6.1",
"description": "Minecraft Pocket Edition 0.6.1 Alpha - Web Build",
"id": "./",
"start_url": "./",
"scope": "./",
"display_override": ["standalone"],
"display": "standalone",
"orientation": "landscape",
"background_color": "#000000",
"theme_color": "#000000",
"icons": [
{
"src": "icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

View File

@@ -167,6 +167,7 @@ options.graphics.fancy=Fancy
options.graphics.fast=Fast options.graphics.fast=Fast
options.guiScale=GUI Scale options.guiScale=GUI Scale
options.guiScale.auto=Auto options.guiScale.auto=Auto
options.guiScale.tiny=Tiny
options.guiScale.small=Small options.guiScale.small=Small
options.guiScale.medium=Medium options.guiScale.medium=Medium
options.guiScale.large=Large options.guiScale.large=Large

View File

@@ -167,6 +167,7 @@ options.graphics.fancy=Fancy
options.graphics.fast=Fast options.graphics.fast=Fast
options.guiScale=GUI Scale options.guiScale=GUI Scale
options.guiScale.auto=Auto options.guiScale.auto=Auto
options.guiScale.tiny=Tiny
options.guiScale.small=Small options.guiScale.small=Small
options.guiScale.medium=Medium options.guiScale.medium=Medium
options.guiScale.large=Large options.guiScale.large=Large

View File

@@ -115,10 +115,11 @@ void NinecraftApp::init()
LOGI("This: %p\n", this); LOGI("This: %p\n", this);
screenChooser.setScreen(SCREEN_STARTMENU); screenChooser.setScreen(SCREEN_STARTMENU);
if (options.getBooleanValue(OPTIONS_FIRST_LAUNCH)) { // Disabled: Show username screen on first launch
options.toggle(OPTIONS_FIRST_LAUNCH); // if (options.getBooleanValue(OPTIONS_FIRST_LAUNCH)) {
setScreen(new UsernameScreen()); // options.toggle(OPTIONS_FIRST_LAUNCH);
} // setScreen(new UsernameScreen());
// }
#else #else
hostMultiplayer(); hostMultiplayer();
#endif #endif

View File

@@ -1125,6 +1125,9 @@ bool Minecraft::useTouchscreen() {
#elif defined(RPI) #elif defined(RPI)
return false; return false;
#endif #endif
if (options.getBooleanValue(OPTIONS_TOUCH_OVERRIDE)) {
return options.getBooleanValue(OPTIONS_USE_TOUCHSCREEN);
}
return options.getBooleanValue(OPTIONS_USE_TOUCHSCREEN) && !_supportsNonTouchscreen; return options.getBooleanValue(OPTIONS_USE_TOUCHSCREEN) && !_supportsNonTouchscreen;
} }
bool Minecraft::supportNonTouchScreen() { bool Minecraft::supportNonTouchScreen() {
@@ -1217,13 +1220,14 @@ void Minecraft::setSize(int w, int h) {
// determine gui scale, optionally overriding auto // determine gui scale, optionally overriding auto
if (guiScale != 0) { if (guiScale != 0) {
// manual selection: 1->small, 2->medium, 3->large, 4->larger, 5->largest // manual selection: 1->tiny, 2->small, 3->medium, 4->large, 5->larger, 6->largest
switch (guiScale) { switch (guiScale) {
case 1: Gui::GuiScale = 2.0f; break; case 1: Gui::GuiScale = 1.0f; break;
case 2: Gui::GuiScale = 3.0f; break; case 2: Gui::GuiScale = 2.0f; break;
case 3: Gui::GuiScale = 4.0f; break; case 3: Gui::GuiScale = 3.0f; break;
case 4: Gui::GuiScale = 5.0f; break; case 4: Gui::GuiScale = 4.0f; break;
case 5: Gui::GuiScale = 6.0f; break; case 5: Gui::GuiScale = 5.0f; break;
case 6: Gui::GuiScale = 6.0f; break;
default: Gui::GuiScale = 1.0f; break; // auto default: Gui::GuiScale = 1.0f; break; // auto
} }
} else { } else {
@@ -1283,16 +1287,15 @@ void Minecraft::setSize(int w, int h) {
void Minecraft::reloadOptions() { void Minecraft::reloadOptions() {
options.save(); options.save();
bool wasTouchscreen = options.getBooleanValue(OPTIONS_USE_TOUCHSCREEN);
options.set(OPTIONS_USE_TOUCHSCREEN, useTouchscreen());
options.save();
if ((wasTouchscreen != useTouchscreen()) || (inputHolder == 0)) bool useTouch = useTouchscreen();
_reloadInput(); _reloadInput();
gui.refreshTouchState();
// user->name = options.username; // user->name = options.username;
LOGI("Reloading-options\n"); LOGI("Reloading-options (touch=%d)\n", useTouch);
// @todo @fix Android and iOS behaves a bit differently when leaving // @todo @fix Android and iOS behaves a bit differently when leaving
// an options screen (Android recreates OpenGL surface) // an options screen (Android recreates OpenGL surface)

View File

@@ -22,3 +22,5 @@ const char* OptionStrings::Controls_AutoJump = "ctrl_autojump";
const char* OptionStrings::Game_DifficultyLevel = "game_difficulty"; const char* OptionStrings::Game_DifficultyLevel = "game_difficulty";
const char* OptionStrings::Tweaks_TouchOverride = "options.touchOverride";

View File

@@ -30,6 +30,7 @@ public:
static const char* Tweaks_Sprint; static const char* Tweaks_Sprint;
static const char* Tweaks_BarOnTop; static const char* Tweaks_BarOnTop;
static const char* Tweaks_TouchOverride;
}; };

View File

@@ -27,14 +27,14 @@ OptionBool autoJump("autoJump", true);
OptionFloat flySpeed("flySpeed", 1.f); OptionFloat flySpeed("flySpeed", 1.f);
OptionFloat cameraSpeed("cameraSpeed", 1.f); OptionFloat cameraSpeed("cameraSpeed", 1.f);
OptionInt guiScale("guiScale", 0, 0, 5); OptionInt guiScale("guiScale", 0, 0, 6);
OptionString skin("skin", "Default"); OptionString skin("skin", "Default");
#ifdef RPI #ifdef RPI
OptionString username("username", "StevePi"); OptionString username("username", "test");
#else #else
OptionString username("username", "Steve"); OptionString username("username", "test");
#endif #endif
OptionBool destroyVibration("destroyVibration", true); OptionBool destroyVibration("destroyVibration", true);
@@ -64,6 +64,8 @@ OptionBool useVignette("useVignette", true);
OptionBool useTouchscreen("useTouchscreen", true); OptionBool useTouchscreen("useTouchscreen", true);
OptionBool touchOverride("touchOverride", false);
OptionBool serverVisible("servervisible", true); OptionBool serverVisible("servervisible", true);
OptionBool foliageTint("foliagetint", true); OptionBool foliageTint("foliagetint", true);
@@ -173,6 +175,8 @@ void Options::initTable() {
m_options[OPTIONS_RESTORED_ANIMS] = &restoredAnims; m_options[OPTIONS_RESTORED_ANIMS] = &restoredAnims;
m_options[OPTIONS_TOUCH_OVERRIDE] = &touchOverride;
m_options[OPTIONS_SERVER_VISIBLE] = &serverVisible; m_options[OPTIONS_SERVER_VISIBLE] = &serverVisible;
m_options[OPTIONS_MENU_STYLE] = &menuStyle; m_options[OPTIONS_MENU_STYLE] = &menuStyle;

View File

@@ -92,6 +92,7 @@ enum OptionId {
OPTIONS_FOG_TYPE, OPTIONS_FOG_TYPE,
OPTIONS_JAVA_HUD, OPTIONS_JAVA_HUD,
OPTIONS_RESTORED_ANIMS, OPTIONS_RESTORED_ANIMS,
OPTIONS_TOUCH_OVERRIDE,
OPTIONS_TINTED_SIDE, OPTIONS_TINTED_SIDE,
OPTIONS_BETA_SKY, OPTIONS_BETA_SKY,
OPTIONS_BEAUTIFUL_SKY, OPTIONS_BEAUTIFUL_SKY,

View File

@@ -423,6 +423,10 @@ void Gui::inventoryUpdated() {
_inventoryNeedsUpdate = true; _inventoryNeedsUpdate = true;
} }
void Gui::refreshTouchState() {
_openInventorySlot = minecraft->useTouchscreen();
}
void Gui::onGraphicsReset() { void Gui::onGraphicsReset() {
inventoryUpdated(); inventoryUpdated();
} }

View File

@@ -71,6 +71,7 @@ public:
void onGraphicsReset(); void onGraphicsReset();
void inventoryUpdated(); void inventoryUpdated();
void refreshTouchState();
void setNowPlaying(const std::string& string); void setNowPlaying(const std::string& string);
void displayClientMessage(const std::string& messageId); void displayClientMessage(const std::string& messageId);

View File

@@ -35,6 +35,17 @@ void GuiElementContainer::removeChild( GuiElement* element ) {
children.erase(it); children.erase(it);
} }
bool GuiElementContainer::containsPointInChildren(int x, int y) const {
for (std::vector<GuiElement*>::const_iterator it = children.begin(); it != children.end(); ++it) {
GuiElement* child = *it;
if (child == NULL || !child->visible) continue;
if (child->pointInside(x, y)) return true;
const GuiElementContainer* container = dynamic_cast<const GuiElementContainer*>(child);
if (container != NULL && container->containsPointInChildren(x, y)) return true;
}
return false;
}
void GuiElementContainer::tick( Minecraft* minecraft ) { void GuiElementContainer::tick( Minecraft* minecraft ) {
for(std::vector<GuiElement*>::iterator it = children.begin(); it != children.end(); ++it) { for(std::vector<GuiElement*>::iterator it = children.begin(); it != children.end(); ++it) {
(*it)->tick(minecraft); (*it)->tick(minecraft);

View File

@@ -13,6 +13,7 @@ public:
virtual void setupPositions(); virtual void setupPositions();
virtual void addChild(GuiElement* element); virtual void addChild(GuiElement* element);
virtual void removeChild(GuiElement* element); virtual void removeChild(GuiElement* element);
bool containsPointInChildren(int x, int y) const;
virtual void tick( Minecraft* minecraft ); virtual void tick( Minecraft* minecraft );

View File

@@ -6,14 +6,36 @@
#include "../../../locale/I18n.h" #include "../../../locale/I18n.h"
#include "TextOption.h" #include "TextOption.h"
#include "KeyOption.h" #include "KeyOption.h"
#include <algorithm>
#include "../Gui.h"
#include "../Screen.h"
#include "../../../platform/input/Mouse.h"
#include "../../../util/Mth.h"
OptionsGroup::OptionsGroup( std::string labelID ) { OptionsGroup::OptionsGroup( std::string labelID )
: contentHeight(0),
scrollOffsetY(0.0f),
maxScrollOffsetY(0.0f),
trackingScrollGesture(false),
scrollingGesture(false),
touchDispatched(false),
dragStartX(0),
dragStartY(0),
lastDragY(0),
touchStartX(0),
touchStartY(0) {
label = I18n::get(labelID); label = I18n::get(labelID);
} }
void OptionsGroup::setupPositions() { void OptionsGroup::setupPositions() {
const int labelHeight = 18;
const int bottomPadding = 36;
const float requestedScroll = scrollOffsetY;
const int scrollOffset = (int)requestedScroll;
int curY = y + labelHeight - scrollOffset;
const int contentStartY = y + labelHeight;
// First we write the header and then we add the items // First we write the header and then we add the items
int curY = y + 18;
for(std::vector<GuiElement*>::iterator it = children.begin(); it != children.end(); ++it) { for(std::vector<GuiElement*>::iterator it = children.begin(); it != children.end(); ++it) {
(*it)->width = width - 5; (*it)->width = width - 5;
@@ -22,16 +44,109 @@ void OptionsGroup::setupPositions() {
(*it)->setupPositions(); (*it)->setupPositions();
curY += (*it)->height + 3; curY += (*it)->height + 3;
} }
height = curY; curY += bottomPadding;
contentHeight = std::max(0, curY - contentStartY + scrollOffset);
maxScrollOffsetY = std::max(0, contentHeight - (height - labelHeight));
const float clampedScroll = Mth::clamp(requestedScroll, 0.0f, maxScrollOffsetY);
if (clampedScroll != requestedScroll) {
scrollOffsetY = clampedScroll;
setupPositions();
}
} }
void OptionsGroup::render( Minecraft* minecraft, int xm, int ym ) { void OptionsGroup::render( Minecraft* minecraft, int xm, int ym ) {
float padX = 10.0f; float padX = 10.0f;
float padY = 5.0f; float padY = 5.0f;
const int labelHeight = 18;
minecraft->font->draw(label, (float)x + padX, (float)y + padY, 0xffffffff, false); minecraft->font->draw(label, (float)x + padX, (float)y + padY, 0xffffffff, false);
glEnable2(GL_SCISSOR_TEST);
glScissor(
Gui::GuiScale * x,
minecraft->height - Gui::GuiScale * (y + height),
Gui::GuiScale * width,
Gui::GuiScale * (height - labelHeight)
);
super::render(minecraft, xm, ym); super::render(minecraft, xm, ym);
glDisable2(GL_SCISSOR_TEST);
}
void OptionsGroup::tick(Minecraft* minecraft) {
int xm = Mouse::getX();
int ym = Mouse::getY();
if (minecraft->screen != NULL) {
minecraft->screen->toGUICoordinate(xm, ym);
}
bool leftDown = Mouse::isButtonDown(MouseAction::ACTION_LEFT);
if (trackingScrollGesture && leftDown) {
int dy = ym - lastDragY;
int dx = xm - dragStartX;
if (!scrollingGesture) {
int totalDx = xm - dragStartX;
int totalDy = ym - dragStartY;
if (std::abs(totalDx) >= ScrollStartThreshold || std::abs(totalDy) >= ScrollStartThreshold) {
if (std::abs(totalDy) >= std::abs(totalDx)) {
scrollingGesture = true;
} else if (!touchDispatched) {
super::mouseClicked(minecraft, touchStartX, touchStartY, MouseAction::ACTION_LEFT);
touchDispatched = true;
}
}
}
if (scrollingGesture && dy != 0) {
scrollByPixels((float)dy);
}
lastDragY = ym;
}
super::tick(minecraft);
}
void OptionsGroup::mouseClicked(Minecraft* minecraft, int x, int y, int buttonNum) {
trackingScrollGesture = false;
scrollingGesture = false;
touchDispatched = false;
if (buttonNum == MouseAction::ACTION_LEFT && pointInside(x, y)) {
trackingScrollGesture = true;
dragStartX = x;
dragStartY = y;
lastDragY = y;
touchStartX = x;
touchStartY = y;
return;
}
super::mouseClicked(minecraft, x, y, buttonNum);
}
void OptionsGroup::mouseReleased(Minecraft* minecraft, int x, int y, int buttonNum) {
bool wasScrolling = scrollingGesture;
bool wasTracking = trackingScrollGesture;
trackingScrollGesture = false;
scrollingGesture = false;
if (buttonNum == MouseAction::ACTION_LEFT && wasTracking && !touchDispatched && pointInside(touchStartX, touchStartY)) {
super::mouseClicked(minecraft, touchStartX, touchStartY, buttonNum);
touchDispatched = true;
}
if (!wasScrolling) {
super::mouseReleased(minecraft, x, y, buttonNum);
}
}
void OptionsGroup::scrollByPixels(float deltaY) {
if (deltaY == 0.0f || maxScrollOffsetY <= 0.0f) return;
scrollOffsetY = Mth::clamp(scrollOffsetY - deltaY, 0.0f, maxScrollOffsetY);
setupPositions();
}
bool OptionsGroup::isScrollingGestureActive() const {
return trackingScrollGesture || scrollingGesture;
} }
OptionsGroup& OptionsGroup::addOptionItem(OptionId optId, Minecraft* minecraft ) { OptionsGroup& OptionsGroup::addOptionItem(OptionId optId, Minecraft* minecraft ) {

View File

@@ -17,7 +17,12 @@ public:
OptionsGroup(std::string labelID); OptionsGroup(std::string labelID);
virtual void setupPositions(); virtual void setupPositions();
virtual void render(Minecraft* minecraft, int xm, int ym); virtual void render(Minecraft* minecraft, int xm, int ym);
virtual void tick(Minecraft* minecraft);
virtual void mouseClicked(Minecraft* minecraft, int x, int y, int buttonNum);
virtual void mouseReleased(Minecraft* minecraft, int x, int y, int buttonNum);
OptionsGroup& addOptionItem(OptionId optId, Minecraft* minecraft); OptionsGroup& addOptionItem(OptionId optId, Minecraft* minecraft);
void scrollByPixels(float deltaY);
bool isScrollingGestureActive() const;
protected: protected:
void createToggle(OptionId optId, Minecraft* minecraft); void createToggle(OptionId optId, Minecraft* minecraft);
@@ -27,6 +32,18 @@ protected:
void createKey(OptionId optId, Minecraft* minecraft); void createKey(OptionId optId, Minecraft* minecraft);
std::string label; std::string label;
int contentHeight;
float scrollOffsetY;
float maxScrollOffsetY;
bool trackingScrollGesture;
bool scrollingGesture;
bool touchDispatched;
int dragStartX;
int dragStartY;
int lastDragY;
int touchStartX;
int touchStartY;
static const int ScrollStartThreshold = 5;
}; };
#endif /*NET_MINECRAFT_CLIENT_GUI_COMPONENTS__OptionsGroup_H__*/ #endif /*NET_MINECRAFT_CLIENT_GUI_COMPONENTS__OptionsGroup_H__*/

View File

@@ -27,11 +27,12 @@ void OptionsItem::render( Minecraft* minecraft, int xm, int ym ) {
std::string scaleText; std::string scaleText;
switch (value) { switch (value) {
case 0: scaleText = I18n::get("options.guiScale.auto"); break; case 0: scaleText = I18n::get("options.guiScale.auto"); break;
case 1: scaleText = I18n::get("options.guiScale.small"); break; case 1: scaleText = I18n::get("options.guiScale.tiny"); break;
case 2: scaleText = I18n::get("options.guiScale.medium"); break; case 2: scaleText = I18n::get("options.guiScale.small"); break;
case 3: scaleText = I18n::get("options.guiScale.large"); break; case 3: scaleText = I18n::get("options.guiScale.medium"); break;
case 4: scaleText = I18n::get("options.guiScale.larger"); break; case 4: scaleText = I18n::get("options.guiScale.large"); break;
case 5: scaleText = I18n::get("options.guiScale.largest"); break; case 5: scaleText = I18n::get("options.guiScale.larger"); break;
case 6: scaleText = I18n::get("options.guiScale.largest"); break;
default: scaleText = I18n::get("options.guiScale.auto"); break; default: scaleText = I18n::get("options.guiScale.auto"); break;
} }
text += ": " + scaleText; text += ": " + scaleText;

View File

@@ -121,6 +121,7 @@ void OptionsScreen::setupPositions() {
(*it)->x = categoryButtons[0]->width; (*it)->x = categoryButtons[0]->width;
(*it)->y = bHeader->height; (*it)->y = bHeader->height;
(*it)->width = width - categoryButtons[0]->width; (*it)->width = width - categoryButtons[0]->width;
(*it)->height = height - bHeader->height;
(*it)->setupPositions(); (*it)->setupPositions();
} }
@@ -147,7 +148,7 @@ void OptionsScreen::removed() {
void OptionsScreen::buttonClicked(Button* button) { void OptionsScreen::buttonClicked(Button* button) {
if (button == btnClose) { if (button == btnClose) {
minecraft->options.save(); minecraft->reloadOptions();
if (minecraft->screen != NULL) { if (minecraft->screen != NULL) {
minecraft->setScreen(NULL); minecraft->setScreen(NULL);
} else { } else {
@@ -194,11 +195,11 @@ void OptionsScreen::generateOptionScreens() {
.addOptionItem(OPTIONS_SENSITIVITY, minecraft); .addOptionItem(OPTIONS_SENSITIVITY, minecraft);
// Game Pane // Game Pane
optionPanes[1]->addOptionItem(OPTIONS_DIFFICULTY, minecraft) optionPanes[1]->addOptionItem(OPTIONS_GUI_SCALE, minecraft)
.addOptionItem(OPTIONS_DIFFICULTY, minecraft)
.addOptionItem(OPTIONS_SERVER_VISIBLE, minecraft) .addOptionItem(OPTIONS_SERVER_VISIBLE, minecraft)
.addOptionItem(OPTIONS_THIRD_PERSON_VIEW, minecraft) .addOptionItem(OPTIONS_THIRD_PERSON_VIEW, minecraft)
.addOptionItem(OPTIONS_WINDOW_SCALE, minecraft) .addOptionItem(OPTIONS_WINDOW_SCALE, minecraft)
.addOptionItem(OPTIONS_GUI_SCALE, minecraft)
.addOptionItem(OPTIONS_SENSITIVITY, minecraft) .addOptionItem(OPTIONS_SENSITIVITY, minecraft)
.addOptionItem(OPTIONS_MUSIC_VOLUME, minecraft) .addOptionItem(OPTIONS_MUSIC_VOLUME, minecraft)
.addOptionItem(OPTIONS_SOUND_VOLUME, minecraft) .addOptionItem(OPTIONS_SOUND_VOLUME, minecraft)
@@ -233,7 +234,8 @@ void OptionsScreen::generateOptionScreens() {
.addOptionItem(OPTIONS_BEAUTIFUL_SKY, minecraft) .addOptionItem(OPTIONS_BEAUTIFUL_SKY, minecraft)
.addOptionItem(OPTIONS_VIGNETTE, minecraft); .addOptionItem(OPTIONS_VIGNETTE, minecraft);
optionPanes[4]->addOptionItem(OPTIONS_ALLOW_SPRINT, minecraft) optionPanes[4]->addOptionItem(OPTIONS_TOUCH_OVERRIDE, minecraft)
.addOptionItem(OPTIONS_ALLOW_SPRINT, minecraft)
.addOptionItem(OPTIONS_BAR_ON_TOP, minecraft) .addOptionItem(OPTIONS_BAR_ON_TOP, minecraft)
.addOptionItem(OPTIONS_MENU_STYLE, minecraft) .addOptionItem(OPTIONS_MENU_STYLE, minecraft)
.addOptionItem(OPTIONS_RPI_CURSOR, minecraft) .addOptionItem(OPTIONS_RPI_CURSOR, minecraft)
@@ -261,11 +263,17 @@ void OptionsScreen::mouseReleased(int x, int y, int buttonNum) {
super::mouseReleased(x, y, buttonNum); super::mouseReleased(x, y, buttonNum);
} }
void OptionsScreen::mouseWheel(int dx, int dy, int xm, int ym) {
if (currentOptionsGroup != NULL && currentOptionsGroup->pointInside(xm, ym) && dy != 0) {
currentOptionsGroup->scrollByPixels((float)dy * 18.0f);
}
}
void OptionsScreen::keyPressed(int eventKey) { void OptionsScreen::keyPressed(int eventKey) {
if (currentOptionsGroup != NULL) if (currentOptionsGroup != NULL)
currentOptionsGroup->keyPressed(minecraft, eventKey); currentOptionsGroup->keyPressed(minecraft, eventKey);
if (eventKey == Keyboard::KEY_ESCAPE) if (eventKey == Keyboard::KEY_ESCAPE)
minecraft->options.save(); minecraft->reloadOptions();
super::keyPressed(eventKey); super::keyPressed(eventKey);
} }

View File

@@ -27,6 +27,7 @@ public:
virtual void mouseClicked(int x, int y, int buttonNum); virtual void mouseClicked(int x, int y, int buttonNum);
virtual void mouseReleased(int x, int y, int buttonNum); virtual void mouseReleased(int x, int y, int buttonNum);
virtual void mouseWheel(int dx, int dy, int xm, int ym);
virtual void keyPressed(int eventKey); virtual void keyPressed(int eventKey);
virtual void charPressed(char inputChar); virtual void charPressed(char inputChar);

View File

@@ -118,22 +118,15 @@ void SimpleChooseLevelScreen::setupPositions()
bGamemode->x = centerX - totalButtonWidth / 2; bGamemode->x = centerX - totalButtonWidth / 2;
bCheats->x = bGamemode->x + buttonWidth + buttonSpacing; bCheats->x = bGamemode->x + buttonWidth + buttonSpacing;
// compute vertical centre for buttons in remaining space // position Survival/Cheats buttons below the seed field
{ int buttonY = tSeed.y + tSeed.height + 20;
int bottomPad = 20; bGamemode->y = buttonY;
int availTop = buttonHeight + 20 + 30 + 10; // just below seed bCheats->y = buttonY;
int availBottom = height - bottomPad - bCreate->height - 10; // leave some gap before create
int availHeight = availBottom - availTop;
if (availHeight < 0) availHeight = 0;
int y = availTop + (availHeight - bGamemode->height) / 2;
bGamemode->y = y;
bCheats->y = y;
}
// position Create button just below with a small gap
bCreate->width = 100; bCreate->width = 100;
bCreate->x = centerX - bCreate->width / 2; bCreate->x = centerX - bCreate->width / 2;
int bottomPadding = 20; bCreate->y = buttonY + bGamemode->height + 40;
bCreate->y = height - bottomPadding - bCreate->height;
} }
void SimpleChooseLevelScreen::tick() void SimpleChooseLevelScreen::tick()

View File

@@ -4,6 +4,7 @@
#include "../../../gui/Gui.h" #include "../../../gui/Gui.h"
#include "../../../renderer/Tesselator.h" #include "../../../renderer/Tesselator.h"
#include "../../../../world/entity/player/Player.h" #include "../../../../world/entity/player/Player.h"
#include "../../../../util/Mth.h"
#include "../../../Minecraft.h" #include "../../../Minecraft.h"
#include "../../../../platform/log.h" #include "../../../../platform/log.h"
@@ -125,48 +126,80 @@ void TouchscreenInput_TestFps::onConfigChanged(const Config& c) {
*/ */
// Code for "D-pad with jump in center" // Code for "D-pad with jump in center"
float Bw = w * 0.11f;//0.08f; // Calculate button size so the full 3x3 grid fits on screen with margins
float Bh = Bw;//0.15f; const float margin = 12.0f;
float availW = w - margin * 2; // horizontal space available
// If too large (like playing on Tablet) float availH = h - margin * 2; // vertical space available
// Each button: 3 wide, 3 tall
float Bw = availW / 3.0f;
float Bh = availH / 3.0f;
// Use the smaller of the two to maintain square buttons
float btnSize = Mth::Min(Bw, Bh);
// Scale down to 65% of max available for a more comfortable, non-obtrusive size
btnSize *= 0.65f;
// Clamp to physical millimeters for consistency across DPIs
PixelCalc& pc = _minecraft->pixelCalc; PixelCalc& pc = _minecraft->pixelCalc;
if (pc.pixelsToMillimeters(Bw) > 200) { //14 float minBtnPx = pc.millimetersToPixels(35); // minimum touch target
Bw = Bh = pc.millimetersToPixels(200); //14 float maxBtnPx = pc.millimetersToPixels(90); // maximum
} if (btnSize < minBtnPx) btnSize = minBtnPx;
if (btnSize > maxBtnPx) btnSize = maxBtnPx;
float Bw2 = btnSize;
float Bh2 = btnSize;
// temp data // temp data
float xx; float xx;
float yy; float yy;
const float BaseY = -8 + h - 3.0f * Bh; // Position from top-left (or top-right for left-handed)
const float BaseX = _options->getBooleanValue(OPTIONS_IS_LEFT_HANDED)? -8 + w - 3 * Bw float dpadTotalW = 3.0f * Bw2;
: 8 + 0; float dpadTotalH = 3.0f * Bh2;
// Place at top of screen instead of bottom
float BaseY = margin;
if (BaseY + dpadTotalH > h - margin) BaseY = h - dpadTotalH - margin;
float BaseX = _options->getBooleanValue(OPTIONS_IS_LEFT_HANDED) ? w - dpadTotalW - margin : margin;
if (BaseX < margin) BaseX = margin;
if (BaseX + dpadTotalW > w - margin) BaseX = w - dpadTotalW - margin;
// Setup the bounding rectangle // Setup the bounding rectangle
_boundingRectangle = RectangleArea(BaseX, BaseY, BaseX + 3 * Bw, BaseY + 3 * Bh); _boundingRectangle = RectangleArea(BaseX, BaseY, BaseX + dpadTotalW, BaseY + dpadTotalH);
xx = BaseX + Bw; yy = BaseY; xx = BaseX + Bw2; yy = BaseY;
_model.addArea(AREA_DPAD_N, aUp = new RectangleArea(xx, yy, xx+Bw, yy+Bh)); _model.addArea(AREA_DPAD_N, aUp = new RectangleArea(xx, yy, xx+Bw2, yy+Bh2));
xx = BaseX; xx = BaseX;
aUpLeft = new RectangleArea(xx, yy, xx+Bw, yy+Bh); aUpLeft = new RectangleArea(xx, yy, xx+Bw2, yy+Bh2);
xx = BaseX + 2 * Bw; xx = BaseX + 2 * Bw2;
aUpRight = new RectangleArea(xx, yy, xx+Bw, yy+Bh); aUpRight = new RectangleArea(xx, yy, xx+Bw2, yy+Bh2);
xx = BaseX + Bw; yy = BaseY + Bh; xx = BaseX + Bw2; yy = BaseY + Bh2;
_model.addArea(AREA_DPAD_C, aJump = new RectangleArea(xx, yy, xx+Bw, yy+Bh)); _model.addArea(AREA_DPAD_C, aJump = new RectangleArea(xx, yy, xx+Bw2, yy+Bh2));
xx = BaseX + Bw; yy = BaseY + 2 * Bh; xx = BaseX + Bw2; yy = BaseY + 2 * Bh2;
_model.addArea(AREA_DPAD_S, aDown = new RectangleArea(xx, yy, xx+Bw, yy+Bh)); _model.addArea(AREA_DPAD_S, aDown = new RectangleArea(xx, yy, xx+Bw2, yy+Bh2));
xx = BaseX; yy = BaseY + Bh; xx = BaseX; yy = BaseY + Bh2;
_model.addArea(AREA_DPAD_W, aLeft = new RectangleArea(xx, yy, xx+Bw, yy+Bh)); _model.addArea(AREA_DPAD_W, aLeft = new RectangleArea(xx, yy, xx+Bw2, yy+Bh2));
xx = BaseX + 2 * Bw; yy = BaseY + Bh; xx = BaseX + 2 * Bw2; yy = BaseY + Bh2;
_model.addArea(AREA_DPAD_E, aRight = new RectangleArea(xx, yy, xx+Bw, yy+Bh)); _model.addArea(AREA_DPAD_E, aRight = new RectangleArea(xx, yy, xx+Bw2, yy+Bh2));
float maxPixels = _minecraft->pixelCalc.millimetersToPixels(10); // Pause and chat buttons - sized relative to D-pad buttons, with bounds checking
// float btnSize = Mth::Min(18 * Gui::GuiScale, maxPixels); float actionBtnSize = Bw2 * 0.7f;
float btnSize = pc.millimetersToPixels(18 * Gui::GuiScale); float actionBtnMargin = 8.0f;
_model.addArea(AREA_PAUSE, aPause = new RectangleArea(w - 4 - btnSize, 4, w - 4, 4 + btnSize)); // Clamp action button size
_model.addArea(AREA_CHAT, aChat = new RectangleArea(w - 8 - btnSize * 2, 4, w - 8 - btnSize, 4 + btnSize)); if (actionBtnSize < pc.millimetersToPixels(30)) actionBtnSize = pc.millimetersToPixels(30);
if (actionBtnSize > pc.millimetersToPixels(120)) actionBtnSize = pc.millimetersToPixels(120);
_model.addArea(AREA_PAUSE, aPause = new RectangleArea(
w - actionBtnMargin - actionBtnSize, actionBtnMargin,
w - actionBtnMargin, actionBtnMargin + actionBtnSize));
_model.addArea(AREA_CHAT, aChat = new RectangleArea(
w - actionBtnMargin * 2 - actionBtnSize * 2, actionBtnMargin,
w - actionBtnMargin * 2 - actionBtnSize, actionBtnMargin + actionBtnSize));
//rebuild(); //rebuild();
} }

View File

@@ -16,7 +16,88 @@
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h> #include <emscripten/emscripten.h>
#endif #endif
static App* g_app = 0; static App* g_app = 0;
#ifdef __EMSCRIPTEN__
static int g_touchPointerIds[Multitouch::MAX_POINTERS];
static void resetTouchPointerIds() {
for (int i = 0; i < Multitouch::MAX_POINTERS; ++i) {
g_touchPointerIds[i] = -1;
}
}
static int getTouchPointerSlot(int touchId) {
for (int i = 0; i < Multitouch::MAX_POINTERS; ++i) {
if (g_touchPointerIds[i] == touchId) return i;
}
return -1;
}
static int allocateTouchPointerSlot(int touchId) {
int slot = getTouchPointerSlot(touchId);
if (slot >= 0) return slot;
for (int i = 0; i < Multitouch::MAX_POINTERS; ++i) {
if (g_touchPointerIds[i] < 0) {
g_touchPointerIds[i] = touchId;
return i;
}
}
return -1;
}
static void releaseTouchPointerSlot(int touchId) {
for (int i = 0; i < Multitouch::MAX_POINTERS; ++i) {
if (g_touchPointerIds[i] == touchId) {
g_touchPointerIds[i] = -1;
return;
}
}
}
static EM_BOOL touch_callback(int eventType, const EmscriptenTouchEvent* touchEvent, void* userData) {
if (!touchEvent) return 0;
const bool isStart = eventType == EMSCRIPTEN_EVENT_TOUCHSTART;
const bool isMove = eventType == EMSCRIPTEN_EVENT_TOUCHMOVE;
const bool isEnd = eventType == EMSCRIPTEN_EVENT_TOUCHEND;
const bool isCancel = eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL;
if (isCancel) {
for (int i = 0; i < Multitouch::MAX_POINTERS; ++i) {
if (g_touchPointerIds[i] >= 0) {
Multitouch::feed(1, 0, Multitouch::getX(i), Multitouch::getY(i), i);
g_touchPointerIds[i] = -1;
}
}
return 1;
}
for (int i = 0; i < touchEvent->numTouches; ++i) {
const EmscriptenTouchPoint& touch = touchEvent->touches[i];
if (!touch.isChanged) continue;
const int slot = isEnd ? getTouchPointerSlot(touch.identifier) : allocateTouchPointerSlot(touch.identifier);
if (slot < 0) continue;
const short x = (short)touch.targetX;
const short y = (short)touch.targetY;
if (isStart) {
Multitouch::feed(1, 1, x, y, slot);
} else if (isMove) {
Multitouch::feed(0, 0, x, y, slot);
} else if (isEnd) {
Multitouch::feed(1, 0, x, y, slot);
releaseTouchPointerSlot(touch.identifier);
}
}
return 1;
}
#endif
int transformKey(int glfwkey) { int transformKey(int glfwkey) {
if (glfwkey >= GLFW_KEY_F1 && glfwkey <= GLFW_KEY_F12) { if (glfwkey >= GLFW_KEY_F1 && glfwkey <= GLFW_KEY_F12) {
@@ -176,12 +257,19 @@ int main(void) {
glfwSetKeyCallback(platform->window, key_callback); glfwSetKeyCallback(platform->window, key_callback);
glfwSetCharCallback(platform->window, character_callback); glfwSetCharCallback(platform->window, character_callback);
glfwSetCursorPosCallback(platform->window, cursor_position_callback); glfwSetCursorPosCallback(platform->window, cursor_position_callback);
glfwSetMouseButtonCallback(platform->window, mouse_button_callback); glfwSetMouseButtonCallback(platform->window, mouse_button_callback);
glfwSetScrollCallback(platform->window, scroll_callback); glfwSetScrollCallback(platform->window, scroll_callback);
glfwSetWindowSizeCallback(platform->window, window_size_callback); glfwSetWindowSizeCallback(platform->window, window_size_callback);
#ifdef __EMSCRIPTEN__
glfwMakeContextCurrent(platform->window); resetTouchPointerIds();
emscripten_set_touchstart_callback("#canvas", 0, 1, touch_callback);
emscripten_set_touchmove_callback("#canvas", 0, 1, touch_callback);
emscripten_set_touchend_callback("#canvas", 0, 1, touch_callback);
emscripten_set_touchcancel_callback("#canvas", 0, 1, touch_callback);
#endif
glfwMakeContextCurrent(platform->window);
#ifndef __EMSCRIPTEN__ #ifndef __EMSCRIPTEN__
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
glfwSwapInterval(0); glfwSwapInterval(0);