Skip to content
Merged

12 29 #253

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
2025-09-11 (12.31)
ANDROID: Implemented support for editing with the system keypad

2025-08-31 (12.30)
ANDROID: Implemented a replacement editor keypad
ANDROID: Implemented editor find command
ANDROID: Updated editor help

2025-04-26 (12.29)
ANDROID: minor fix for PEN(3)

Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ dnl This program is distributed under the terms of the GPL v2.0
dnl Download the GNU Public License (GPL) from www.gnu.org
dnl

AC_INIT([smallbasic], [12.30])
AC_INIT([smallbasic], [12.31])
AC_CONFIG_SRCDIR([configure.ac])

AC_CANONICAL_TARGET
Expand Down
17 changes: 17 additions & 0 deletions src/platform/android/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
# Download the GNU Public License (GPL) from www.gnu.org
#

#
# For investigating crash reports in play-studio, for example:
# aarch64-linux-android-addr2line -e symbols/v12.31-87/arm64-v8a/libsmallbasic.so.dbg -f -C 0x00000000000d2dd0
#

all-am: Makefile

build-test: ndk-build-test
Expand Down Expand Up @@ -32,6 +37,18 @@ ndk-build-release:

release:
(./gradlew clean :app:bundle)
@echo "Saving debug symbols..."
@VERSION_CODE=$$(sed -n 's/^[[:space:]]*versionCode[[:space:]]*\([0-9]*\).*/\1/p' app/build.gradle); \
VERSION_NAME=$$(sed -n "s/^[[:space:]]*versionName[[:space:]]*['\"]\\([^'\"]*\\)['\"].*/\\1/p" app/build.gradle); \
SYMBOL_DIR="symbols/v$${VERSION_NAME}-$${VERSION_CODE}"; \
mkdir -p "$${SYMBOL_DIR}"; \
if [ -d app/build/intermediates/native_debug_metadata/release/extractReleaseNativeDebugMetadata/out ]; then \
cp -r app/build/intermediates/native_debug_metadata/release/extractReleaseNativeDebugMetadata/out/* "$${SYMBOL_DIR}/"; \
echo "Symbols saved to $${SYMBOL_DIR}/"; \
ls -lh "$${SYMBOL_DIR}"/*/*.dbg; \
else \
echo "Warning: debug symbols not found"; \
fi

library:
(./gradlew clean assemble)
Expand Down
4 changes: 2 additions & 2 deletions src/platform/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ android {
applicationId 'net.sourceforge.smallbasic'
minSdkVersion 21
targetSdkVersion 36
versionCode 83
versionName '12.30'
versionCode 88
versionName '12.31'
resourceConfigurations += ['en']
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,15 @@
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresPermission;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.core.graphics.Insets;
import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import java.io.BufferedReader;
import java.io.BufferedWriter;
Expand Down Expand Up @@ -121,7 +126,7 @@ public class MainActivity extends NativeActivity {
public static native boolean libraryMode();
public static native void onActivityPaused(boolean paused);
public static native void onBack();
public static native void onResize(int width, int height);
public static native void onResize(int width, int height, int imeState);
public static native void onUnicodeChar(int ch);
public static native boolean optionSelected(int index);
public static native void runFile(String fileName);
Expand Down Expand Up @@ -429,6 +434,10 @@ public int getWindowHeight() {
return rect.height();
}

public boolean isInsetBasedOnResize() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
}

public boolean isPredictiveBack() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
}
Expand All @@ -452,10 +461,11 @@ public boolean loadModules() {
@Override
public void onGlobalLayout() {
super.onGlobalLayout();
// find the visible coordinates of our view
Rect rect = new Rect();
findViewById(android.R.id.content).getWindowVisibleDisplayFrame(rect);
onResize(rect.width(), rect.height());
if (!isInsetBasedOnResize()) {
Rect rect = new Rect();
findViewById(android.R.id.content).getWindowVisibleDisplayFrame(rect);
onResize(rect.width(), rect.height(), 0);
}
}

@Override
Expand Down Expand Up @@ -774,6 +784,7 @@ protected void onCreate(Bundle savedInstanceState) {
setImmersiveMode();
setupStorageEnvironment();
setupPredictiveBack();
setupInsetBaseOnResize();
if (!libraryMode()) {
processIntent();
processSettings();
Expand Down Expand Up @@ -1063,14 +1074,42 @@ private void setImmersiveMode() {
}
}

/**
* onResize() handler for android 16+
*/
private void setupInsetBaseOnResize() {
if (isInsetBasedOnResize()) {
View view = getWindow().getDecorView();
ViewCompat.setOnApplyWindowInsetsListener(view, new OnApplyWindowInsetsListener() {
@NonNull
@Override
public WindowInsetsCompat onApplyWindowInsets(@NonNull View view, @NonNull WindowInsetsCompat insets) {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
Insets navInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars());
Insets ime = insets.getInsets(WindowInsetsCompat.Type.ime());
boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
int bottomInset = Math.max(systemBars.bottom, ime.bottom);
int width = view.getWidth() - navInsets.right;
int height = view.getHeight() - (systemBars.top + bottomInset);
view.setPadding(0, 0, navInsets.right, navInsets.bottom);
if (width > 0 && height > 0) {
// ignore spurious transitional values
onResize(width, height, imeVisible ? 1 : -1);
}
return insets;
}
});
view.post(() -> ViewCompat.requestApplyInsets(view));
}
}

//
// Hook into Predictive Back (Android 13+)
//
private void setupPredictiveBack() {
if (isPredictiveBack()) {
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
new OnBackInvokedCallback() {
OnBackInvokedDispatcher.PRIORITY_OVERLAY, new OnBackInvokedCallback() {
@Override
public void onBackInvoked() {
Log.d(TAG, "onBackInvoked");
Expand Down Expand Up @@ -1226,7 +1265,8 @@ protected Response getFile(String remoteHost, String path, boolean asset) throws
String name = "webui/" + path;
long length = getFileLength(name);
log("Opened " + name + " " + length + " bytes");
String contentType = path.endsWith("js") ? "text/javascript" : "text/html";
String contentType = path.endsWith("js") ? "text/javascript" :
path.endsWith("css") ? "text/css": "text/html";
result = new Response(getAssets().open(name), length, contentType);
if ("index.html".equals(path) && isHostNotPermitted(remoteHost)) {
requestHostPermission(remoteHost);
Expand Down
2 changes: 1 addition & 1 deletion src/platform/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.12.1'
classpath 'com.android.tools.build:gradle:8.13.0'
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/platform/android/jni/display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ bool Canvas::create(int w, int h) {
return result;
}

void Canvas::drawRegion(Canvas *src, const MARect *srcRect, int destX, int destY) {
void Canvas::drawRegion(Canvas *src, const MARect *srcRect, int destX, int destY) const {
int srcH = srcRect->height;
if (srcRect->top + srcRect->height > src->_h) {
srcH = src->_h - srcRect->top;
Expand All @@ -65,7 +65,7 @@ void Canvas::drawRegion(Canvas *src, const MARect *srcRect, int destX, int destY
}
}

void Canvas::fillRect(int left, int top, int width, int height, pixel_t drawColor) {
void Canvas::fillRect(int left, int top, int width, int height, pixel_t drawColor) const {
int dtX = x();
int dtY = y();
uint8_t dR, dG, dB;
Expand Down
21 changes: 15 additions & 6 deletions src/platform/android/jni/editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ struct StatusMessage {
out->setStatus(message);
}

private:
private:
bool _dirty;
bool _find;
int _scroll;
Expand All @@ -126,7 +126,6 @@ void showHelp(TextEditHelpWidget *helpWidget) {
void Runtime::editSource(strlib::String loadPath, bool restoreOnExit) {
logEntered();

showKeypad(false);
int w = _output->getWidth();
int h = _output->getHeight();
int charWidth = _output->getCharWidth();
Expand All @@ -153,11 +152,16 @@ void Runtime::editSource(strlib::String loadPath, bool restoreOnExit) {
_output->addInput(editWidget);
_output->addInput(helpWidget);

if (_keypad != nullptr) {
_output->addInput(_keypad);
if (opt_ide == IDE_EXTERNAL) {
showKeypad(true);
} else {
_keypad = new KeypadInput(w, false, false, charWidth, charHeight);
_output->addInput(_keypad);
showKeypad(false);
if (_keypad != nullptr) {
_output->addInput(_keypad);
} else {
_keypad = new KeypadInput(w, false, false, charWidth, charHeight);
_output->addInput(_keypad);
}
}

statusMessage.update(editWidget, _output);
Expand Down Expand Up @@ -207,6 +211,7 @@ void Runtime::editSource(strlib::String loadPath, bool restoreOnExit) {
} else {
widget = helpWidget;
showHelp(helpWidget);
showKeypad(false);
}
break;
case SB_KEY_F(9):
Expand Down Expand Up @@ -258,6 +263,9 @@ void Runtime::editSource(strlib::String loadPath, bool restoreOnExit) {
}

if ((exitHelp || isBack()) && widget == helpWidget) {
if (opt_ide == IDE_EXTERNAL) {
showKeypad(true);
}
widget = editWidget;
helpWidget->hide();
editWidget->setFocus(true);
Expand Down Expand Up @@ -293,6 +301,7 @@ void Runtime::editSource(strlib::String loadPath, bool restoreOnExit) {
if (!_output->removeInput(_keypad)) {
trace("Failed to remove keypad input");
}
showKeypad(false);
_editor = editWidget;
_editor->setFocus(false);
} else {
Expand Down
16 changes: 10 additions & 6 deletions src/platform/android/jni/runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,9 @@ extern "C" JNIEXPORT void JNICALL Java_net_sourceforge_smallbasic_MainActivity_s
}

extern "C" JNIEXPORT void JNICALL Java_net_sourceforge_smallbasic_MainActivity_onResize
(JNIEnv *env, jclass jclazz, jint width, jint height) {
(JNIEnv *env, jclass jclazz, jint width, jint height, jint imeState) {
if (runtime != nullptr && !runtime->isClosing() && runtime->isActive() && os_graphics) {
runtime->onResize(width, height);
runtime->onResize(width, height, imeState);
}
}

Expand Down Expand Up @@ -258,7 +258,7 @@ extern "C" JNIEXPORT void JNICALL Java_net_sourceforge_smallbasic_MainActivity_c

void onContentRectChanged(ANativeActivity *activity, const ARect *rect) {
logEntered();
runtime->onResize(rect->right, rect->bottom);
runtime->onResize(rect->right, rect->bottom, 0);
}

jbyteArray newByteArray(JNIEnv *env, const char *str) {
Expand Down Expand Up @@ -619,7 +619,7 @@ void Runtime::loadConfig() {
int height = getInteger("getWindowHeight");
if (height != _graphics->getHeight()) {
// height adjustment for bottom virtual navigation bar
onResize(_graphics->getWidth(), height);
onResize(_graphics->getWidth(), height, 0);
}

_output->setTextColor(DEFAULT_FOREGROUND, DEFAULT_BACKGROUND);
Expand Down Expand Up @@ -990,14 +990,18 @@ void Runtime::showKeypad(bool show) {
_app->activity->vm->DetachCurrentThread();
}

void Runtime::onResize(int width, int height) {
void Runtime::onResize(int width, int height, int imeState) {
logEntered();
if (_graphics != nullptr) {
int w = _graphics->getWidth();
int h = _graphics->getHeight();
if (w != width || h != height) {
trace("Resized from %d %d to %d %d", w, h, width, height);
trace("Resized from %d %d to %d %d [ime=%d]", w, h, width, height, imeState);
ALooper_acquire(_app->looper);
if (imeState != 0) {
// in android 16+ when resize also knows whether the ime (keypad) is active
_keypadActive = (imeState > 0);
}
_graphics->setSize(width, height);
auto *maEvent = new MAEvent();
maEvent->type = EVENT_TYPE_SCREEN_CHANGED;
Expand Down
2 changes: 1 addition & 1 deletion src/platform/android/jni/runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ struct Runtime : public System {
void showCursor(CursorType cursorType) override {}
void showKeypad(bool show);
void onPaused(bool paused) { if (_graphics != nullptr) _graphics->onPaused(paused); }
void onResize(int w, int h);
void onResize(int w, int h, int imeState);
void onRunCompleted() override;
void onUnicodeChar(int ch);
void loadConfig();
Expand Down
8 changes: 8 additions & 0 deletions src/platform/web/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ MHD_Response *serve_file(const char *path) {
int fd = open(path, O_RDONLY | O_BINARY);
if (!fstat(fd, &stbuf)) {
response = MHD_create_response_from_fd(stbuf.st_size, fd);

// add the content-type headers for browsing
unsigned len = strlen(path);
if (len > 4 && strcmp(path + len - 4, ".css") == 0) {
MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/css");
} else if (len > 3 && strcmp(path + len - 3, ".js") == 0) {
MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/javascript");
}
} else {
response = nullptr;
}
Expand Down
4 changes: 2 additions & 2 deletions src/ui/canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ struct Canvas {
virtual ~Canvas();

bool create(int w, int h);
void drawRegion(Canvas *src, const MARect *srcRect, int dstx, int dsty);
void fillRect(int x, int y, int w, int h, pixel_t color);
void drawRegion(Canvas *src, const MARect *srcRect, int dstx, int dsty) const;
void fillRect(int x, int y, int w, int h, pixel_t color) const;
void setClip(int x, int y, int w, int h);
pixel_t *getLine(int y) const { return _pixels + (y * _w); }
int x() const { return _clip ? _clip->left : 0; }
Expand Down
Loading