Skip to content
Merged
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

Large diffs are not rendered by default.

183 changes: 132 additions & 51 deletions app/src/main/java/com/maxistar/textpad/activities/EditorActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import android.view.MotionEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewParent;
import android.view.WindowInsets;
import android.view.inputmethod.EditorInfo;
import android.webkit.WebView;
Expand Down Expand Up @@ -190,6 +191,11 @@ private static final class SaveRequest {

WebView mWebView;

private int cachedMaxHorizontalScroll = 0;
private int cachedHorizontalScrollEditorWidth = -1;
private boolean horizontalScrollBoundsDirty = true;
private int horizontalScrollBoundsCalculationCount = 0;

/**
* Called when the activity is first created.
*/
Expand Down Expand Up @@ -217,9 +223,13 @@ public void onCreate(Bundle savedInstanceState) {
mText = this.findViewById(R.id.editText1);
mText.setBackgroundResource(android.R.color.transparent);
mText.setOnTouchListener(new TwoFingerPanTouchListener());
if (!settingsService.isAutoWrapping()) {
disableEditorAutowrapping();
}
mText.addOnLayoutChangeListener((view, left, top, right, bottom,
oldLeft, oldTop, oldRight, oldBottom) -> {
if ((right - left) != (oldRight - oldLeft)
|| (bottom - top) != (oldBottom - oldTop)) {
invalidateHorizontalScrollBounds();
}
});
editTextUndoRedo = new EditTextUndoRedo(mText, this);
setTextWatcher();

Expand All @@ -229,6 +239,9 @@ public void onCreate(Bundle savedInstanceState) {
scrollView = findViewById(R.id.vscroll);
}
applyPreferences();
if (!settingsService.isAutoWrapping()) {
disableEditorAutowrapping();
}

if (savedInstanceState != null) {
restoreState(savedInstanceState);
Expand Down Expand Up @@ -338,6 +351,7 @@ public void beforeTextChanged(CharSequence s, int start, int count, int after) {

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
invalidateHorizontalScrollBounds();
if (suppressRecoveryTracking) {
return;
}
Expand Down Expand Up @@ -759,12 +773,14 @@ private boolean isFilenameEmpty() {
}

void applyPreferences() {
invalidateHorizontalScrollBounds();
applyFontFace();
applyFontSize();
applyColors();
}

private void disableEditorAutowrapping() {
invalidateHorizontalScrollBounds();
mText.setHorizontallyScrolling(true);
mText.setHorizontalScrollBarEnabled(true);
mText.setMaxLines(Integer.MAX_VALUE);
Expand Down Expand Up @@ -865,6 +881,9 @@ public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem redoMenu = menu.findItem(R.id.menu_edit_redo);
redoMenu.setEnabled(editTextUndoRedo.getCanRedo());

MenuItem goToMenu = menu.findItem(R.id.menu_document_go_to);
goToMenu.setEnabled(mText.length() > 0);

updateRecentFiles(menu);

if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
Expand Down Expand Up @@ -996,8 +1015,10 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
saveFile();
} else if (itemId == R.id.menu_document_save_as) {
saveAs();
} else if (itemId == R.id.menu_document_go_to) {
moveCaretPosition();
} else if (itemId == R.id.menu_document_go_to_beginning) {
moveCaretPosition(0);
} else if (itemId == R.id.menu_document_go_to_end) {
moveCaretPosition(mText.length());
} else if (itemId == R.id.menu_edit_undo) {
editUndo();
} else if (itemId == R.id.menu_edit_redo) {
Expand Down Expand Up @@ -1969,49 +1990,90 @@ protected void showToast(String toast_str) {
private class TwoFingerPanTouchListener implements View.OnTouchListener {

private boolean panningActive = false;
private boolean panGestureOwned = false;
private float lastFocalX;
private float lastFocalY;
private int maxScrollX;
private int maxScrollY;

@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
panningActive = false;
endPanGesture(view);
return false;
case MotionEvent.ACTION_POINTER_DOWN:
if (event.getPointerCount() == 2) {
panningActive = true;
view.getParent().requestDisallowInterceptTouchEvent(true);
lastFocalX = focalX(event);
lastFocalY = focalY(event);
startPanning(view, event);
} else {
stopActivePanning(view);
}
return panningActive;
return panGestureOwned;
case MotionEvent.ACTION_MOVE:
if (!panningActive) {
if (!panGestureOwned) {
return false;
}
if (!panningActive) {
return true;
}
if (event.getPointerCount() != 2) {
stopActivePanning(view);
return true;
}
float focalX = focalX(event);
float focalY = focalY(event);
panContent(
Math.round(focalX - lastFocalX),
Math.round(focalY - lastFocalY)
Math.round(focalY - lastFocalY),
maxScrollX,
maxScrollY
);
lastFocalX = focalX;
lastFocalY = focalY;
return true;
case MotionEvent.ACTION_POINTER_UP:
if (panningActive) {
view.getParent().requestDisallowInterceptTouchEvent(false);
if (panGestureOwned) {
stopActivePanning(view);
return true;
}
return panningActive;
return false;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
boolean wasPanning = panningActive;
panningActive = false;
view.getParent().requestDisallowInterceptTouchEvent(false);
return wasPanning;
return endPanGesture(view);
default:
return panningActive;
return panGestureOwned;
}
}

private void startPanning(View view, MotionEvent event) {
panningActive = true;
panGestureOwned = true;
requestParentInterception(view, true);
lastFocalX = focalX(event);
lastFocalY = focalY(event);
Layout layout = mText.getLayout();
maxScrollX = getMaxHorizontalScroll(layout);
maxScrollY = calculateMaxVerticalScroll(layout);
}

private void stopActivePanning(View view) {
if (panningActive) {
requestParentInterception(view, false);
}
panningActive = false;
}

private boolean endPanGesture(View view) {
boolean shouldConsume = panGestureOwned || panningActive;
stopActivePanning(view);
panGestureOwned = false;
return shouldConsume;
}

private void requestParentInterception(View view, boolean disallow) {
ViewParent parent = view.getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(disallow);
}
}

Expand All @@ -2032,18 +2094,11 @@ private float focalY(MotionEvent event) {
}
}

private void panContent(int deltaX, int deltaY) {
Layout layout = mText.getLayout();
int maxX = layout == null ? 0 :
layout.getWidth() + mText.getTotalPaddingLeft() + mText.getTotalPaddingRight()
- mText.getWidth();
int newX = clampScroll(mText.getScrollX() - deltaX, maxX);
private void panContent(int deltaX, int deltaY, int maxScrollX, int maxScrollY) {
int newX = clampScroll(mText.getScrollX() - deltaX, maxScrollX);

if (simpleScrolling()) {
int maxY = layout == null ? 0 :
layout.getHeight() + mText.getTotalPaddingTop() + mText.getTotalPaddingBottom()
- mText.getHeight();
int newY = clampScroll(mText.getScrollY() - deltaY, maxY);
int newY = clampScroll(mText.getScrollY() - deltaY, maxScrollY);
mText.scrollTo(newX, newY);
return;
}
Expand All @@ -2054,14 +2109,50 @@ private void panContent(int deltaX, int deltaY) {
mText.scrollTo(newX, mText.getScrollY());
}

private int clampScroll(int value, int max) {
if (value < 0) {
private int getMaxHorizontalScroll(Layout layout) {
int editorWidth = mText.getWidth();
if (layout == null || editorWidth <= 0) {
return 0;
}
if (max > 0 && value > max) {
return max;
if (horizontalScrollBoundsDirty || cachedHorizontalScrollEditorWidth != editorWidth) {
cachedMaxHorizontalScroll = calculateMaxHorizontalScroll(layout, editorWidth);
cachedHorizontalScrollEditorWidth = editorWidth;
horizontalScrollBoundsDirty = false;
}
return value;
return cachedMaxHorizontalScroll;
}

private void invalidateHorizontalScrollBounds() {
horizontalScrollBoundsDirty = true;
}

public int getHorizontalScrollBoundsCalculationCountForTests() {
return horizontalScrollBoundsCalculationCount;
}

private int calculateMaxHorizontalScroll(Layout layout, int editorWidth) {
horizontalScrollBoundsCalculationCount++;
float maxLineWidth = 0;
for (int line = 0; line < layout.getLineCount(); line++) {
maxLineWidth = Math.max(maxLineWidth, layout.getLineWidth(line));
Comment on lines +2135 to +2137

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid scanning every line when a pan begins

For large documents, placing the second finger now iterates through every layout line on the UI thread before any panning can occur. Since this scan runs at the start of every two-finger gesture, files with hundreds of thousands of lines can visibly freeze the editor whenever the user begins scrolling; cache the maximum width and invalidate it when the text or layout changes instead of recomputing it per gesture.

Useful? React with 👍 / 👎.

}
int contentWidth = (int) Math.ceil(maxLineWidth)
+ mText.getTotalPaddingLeft() + mText.getTotalPaddingRight();
return Math.max(0, contentWidth - editorWidth);
}

private int calculateMaxVerticalScroll(Layout layout) {
if (layout == null) {
return 0;
}
int contentHeight = layout.getHeight()
+ mText.getTotalPaddingTop() + mText.getTotalPaddingBottom();
return Math.max(0, contentHeight - mText.getHeight());
}

private int clampScroll(int value, int max) {
int boundedMax = Math.max(0, max);
return Math.max(0, Math.min(value, boundedMax));
}

// QueryTextListener
Expand Down Expand Up @@ -2178,20 +2269,10 @@ public boolean onMenuItemActionCollapse(MenuItem menuItem) {
}
}

private void moveCaretPosition() {
if (mText.length() != 0) {
new AlertDialog.Builder(this)
.setIcon(android.R.drawable.ic_menu_directions)
.setTitle(R.string.Go_To_Title)
.setMessage(R.string.Go_To_Description)
.setPositiveButton(R.string.Go_To_End,
(dialog, which) -> {
mText.setSelection(mText.length());
})
.setNegativeButton(R.string.Go_To_Beginning,
(dialog, which) -> {
mText.setSelection(0);
}).show();
}
private void moveCaretPosition(int position) {
mText.requestFocus();
mText.setSelection(position);
View editorRoot = findViewById(R.id.editor_root);
mText.post(() -> requestFocusedCaretOnScreen(editorRoot));
}
}
13 changes: 11 additions & 2 deletions app/src/main/res/menu/main_menu.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,16 @@
android:id="@+id/menu_document_go_to"
android:title="@string/Go_To"
android:icon="@drawable/ic_arrow_forward"
app:iconTint="@color/colorIcon" />
app:iconTint="@color/colorIcon">
<menu>
<item
android:id="@+id/menu_document_go_to_beginning"
android:title="@string/Go_To_Beginning" />
<item
android:id="@+id/menu_document_go_to_end"
android:title="@string/Go_To_End" />
</menu>
</item>
<item
android:id="@+id/menu_document_share"
android:title="@string/Menu_Share"
Expand All @@ -110,4 +119,4 @@
android:title="@string/Exit"
android:icon="@drawable/ic_arrow_right"
app:iconTint="@color/colorIcon" />
</menu>
</menu>
2 changes: 0 additions & 2 deletions app/src/main/res/values-ar/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
<string name="Save">حفظ</string>
<string name="Save_As">حفظ كـ</string>
<string name="Go_To">الانتقال إلى</string>
<string name="Go_To_Title">الانتقال إلى الموضع</string>
<string name="Go_To_Description">إلى أين تريد نقل المؤشر؟</string>
<string name="Go_To_Beginning">البداية</string>
<string name="Go_To_End">النهاية</string>
<string name="Location">الموقع: %s</string>
Expand Down
2 changes: 0 additions & 2 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
<string name="Save">Speichern</string>
<string name="Save_As">Speichern unter</string>
<string name="Go_To">Gehe zu</string>
<string name="Go_To_Title">Zu Position gehen</string>
<string name="Go_To_Description">Wohin möchten Sie den Cursor bewegen?</string>
<string name="Go_To_Beginning">Anfang</string>
<string name="Go_To_End">Ende</string>
<string name="Location">Lage: %s</string>
Expand Down
2 changes: 0 additions & 2 deletions app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
<string name="Save">Guardar</string>
<string name="Save_As">Guardar como</string>
<string name="Go_To">Ir a</string>
<string name="Go_To_Title">Ir a la posición</string>
<string name="Go_To_Description">¿Dónde quieres mover el cursor?</string>
<string name="Go_To_Beginning">Principio</string>
<string name="Go_To_End">Final</string>
<string name="Location">Ubicación: %s</string>
Expand Down
2 changes: 0 additions & 2 deletions app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
<string name="Save">Enregistrer</string>
<string name="Save_As">Enregistrer sous</string>
<string name="Go_To">Aller à</string>
<string name="Go_To_Title">Aller à la position</string>
<string name="Go_To_Description">Où souhaitez-vous déplacer le curseur ?</string>
<string name="Go_To_Beginning">Début</string>
<string name="Go_To_End">Fin</string>
<string name="Location">Emplacement : %s</string>
Expand Down
2 changes: 0 additions & 2 deletions app/src/main/res/values-hi/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
<string name="Save">सहेजें</string>
<string name="Save_As">इस रूप में सहेजें</string>
<string name="Go_To">पर जाएँ</string>
<string name="Go_To_Title">स्थिति पर जाएँ</string>
<string name="Go_To_Description">आप कर्सर को कहाँ ले जाना चाहते हैं?</string>
<string name="Go_To_Beginning">शुरुआत</string>
<string name="Go_To_End">अंत</string>
<string name="Location">स्थान: %s</string>
Expand Down
2 changes: 0 additions & 2 deletions app/src/main/res/values-it/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
<string name="Save">Salva</string>
<string name="Save_As">Salva come</string>
<string name="Go_To">Vai a</string>
<string name="Go_To_Title">Vai alla posizione</string>
<string name="Go_To_Description">Dove vuoi spostare il cursore?</string>
<string name="Go_To_Beginning">Inizio</string>
<string name="Go_To_End">Fine</string>
<string name="Location">Posizione: %s</string>
Expand Down
2 changes: 0 additions & 2 deletions app/src/main/res/values-ja/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
<string name="Save">上書き保存</string>
<string name="Save_As">別名で保存</string>
<string name="Go_To">移動</string>
<string name="Go_To_Title">位置に移動</string>
<string name="Go_To_Description">カーソルをどこに移動しますか?</string>
<string name="Go_To_Beginning">先頭</string>
<string name="Go_To_End">末尾</string>
<string name="Location">パス: %s</string>
Expand Down
Loading