Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
return;
}

_lineNumbersView.setup(_hlEditor);
_lineNumbersView.setEditText(_hlEditor);
_lineNumbersView.setLineNumbersEnabled(_appSettings.getDocumentLineNumbersEnabled(_document.path));

// Upon construction, the document format has been determined from extension etc
Expand Down Expand Up @@ -191,6 +191,7 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
_hlEditor.setAutoFormatEnabled(_appSettings.getDocumentAutoFormatEnabled(_document.path));
_hlEditor.setSaveInstanceState(false); // We will reload from disk
_hlEditor.setOverScrollMode(View.OVER_SCROLL_ALWAYS);
_hlEditor.setStaticCursorEnabled(_appSettings.isStaticCursorEnabled());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Do not need to send contents to accessibility
_hlEditor.setImportantForAccessibility(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,24 @@
import android.text.Layout;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.ActionMode;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.widget.EditText;
import android.widget.Toast;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.core.content.ContextCompat;

import net.gsantner.markor.R;
import net.gsantner.markor.activity.MainActivity;
Expand Down Expand Up @@ -74,6 +78,7 @@ public class HighlightingEditor extends AppCompatEditText {
private final AtomicBoolean _textUnchangedWhileHighlighting = new AtomicBoolean(true);
private int _textChangedNumber;
private final Runnable _textChangedRecorder = TextViewUtils.makeDebounced(getHandler(), 1000, () -> _textChangedNumber++);
private StaticCursorDrawer _staticCursorDrawer;

public HighlightingEditor(Context context, AttributeSet attrs) {
super(context, attrs);
Expand Down Expand Up @@ -122,32 +127,12 @@ public void afterTextChanged(final Editable s) {
setupCustomOptions();
}

@Override
public boolean onPreDraw() {
try {
return super.onPreDraw();
} catch (OutOfMemoryError ignored) {
return false; // return false to cancel current drawing pass/round
}
}

@Override
protected void onDraw(Canvas canvas) {
try {
super.onDraw(canvas);
} catch (Exception e) {
// Hinder drawing from crashing the app
Log.e(getClass().getName(), "HighlightingEdtior onDraw->super.onDraw crash" + e);
Toast.makeText(getContext(), e.toString(), Toast.LENGTH_SHORT).show();
}
}

// Highlighting
// ---------------------------------------------------------------------------------------------

// Batch edit spans (or anything else, really)
// This triggers a reflow which will bring focus back to the cursor.
// Therefore it cannot be used for updating the highlighting as one scrolls
// Therefore, it cannot be used for updating the highlighting as one scrolls
private void batch(final Runnable runnable) {
try {
beginBatchEdit();
Expand Down Expand Up @@ -355,6 +340,30 @@ public void setSaveInstanceState(final boolean save) {
_saveInstanceState = save;
}

@Override
public boolean onPreDraw() {
try {
return super.onPreDraw();
} catch (OutOfMemoryError ignored) {
return false; // return false to cancel current drawing pass/round
}
}

@Override
protected void onDraw(Canvas canvas) {
try {
super.onDraw(canvas);

if (_staticCursorDrawer != null && hasFocus()) {
_staticCursorDrawer.draw(canvas);
}
} catch (Exception e) {
// Hinder drawing from crashing the app
Log.e(getClass().getName(), "HighlightingEditor onDraw->super.onDraw crash" + e);
Toast.makeText(getContext(), e.toString(), Toast.LENGTH_SHORT).show();
}
}

@Override
public Parcelable onSaveInstanceState() {
// Call is always required
Expand All @@ -379,6 +388,14 @@ public void setTextSize(float size) {
}
}

@Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
if (_staticCursorDrawer != null) {
_staticCursorDrawer.notifyTextSizeChanged();
}
}

@Override
public void setText(final CharSequence text, final BufferType type) {
super.setText(text, type);
Expand Down Expand Up @@ -412,7 +429,7 @@ public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
}
}

// Hleditor will report that it is not autofillable under certain circumstances
// HighlightingEditor will report that it is not auto-fillable under certain circumstances
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public int getAutofillType() {
Expand Down Expand Up @@ -452,14 +469,12 @@ protected void onSelectionChanged(int selStart, int selEnd) {
if (MainActivity.IS_DEBUG_ENABLED) {
AppSettings.appendDebugLog("Selection changed: " + selStart + "->" + selEnd);
}
}

@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (_staticCursorDrawer != null) {
_staticCursorDrawer.notifySelectionChanged(selStart, selEnd);
}
}


// Auto-format
// ---------------------------------------------------------------------------------------------

Expand Down Expand Up @@ -608,4 +623,109 @@ public void onDestroyActionMode(ActionMode mode) {
public int getTextChangedNumber() {
return _textChangedNumber;
}

// Static cursor (redraw cursor)
// ---------------------------------------------------------------------------------------------

/**
* Static cursor drawer for EditText.
*/
static class StaticCursorDrawer {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

General note for the future:

One of the annoying things in todo.txt is that increasing the line spacing also grows the cursor height for the first and last visual line per item. If we are going to draw the cursor, we can manually force the cursor height to be consistent

Copy link
Copy Markdown
Contributor Author

@guanglinn guanglinn Apr 24, 2026

Choose a reason for hiding this comment

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

Yes, it can solve that problem in todo.txt

This is before

Screenshot_20260425_061459.jpg

This is now, enabled static cursor

Screenshot_20260427_114605_net.gsantner.markor_test_edit_1054853967512997.jpg

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is very nice! Thank you!


private final Paint paint = new Paint();
private final EditText editText;

private float lineHeight;
private float offsetY;
private final float offsetYBase;
private boolean paused;

public StaticCursorDrawer(final @NonNull EditText editText, final @ColorInt int cursorColor) {
this.editText = editText;
paint.setColor(cursorColor);
offsetYBase = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, editText.getResources().getDisplayMetrics());
notifyTextSizeChanged(); // Initialize textSize, lineHeight, offsetY and the cursor width
}

/**
* Draw static cursor.
*
* @param canvas The canvas of the EditText.
*/
public void draw(final Canvas canvas) {
if (paused) {
return;
}

final Layout layout = editText.getLayout();
if (layout == null) {
return;
}

// Draw static cursor
final int selectionStart = editText.getSelectionStart();
final int line = layout.getLineForOffset(selectionStart);
final float x = layout.getPrimaryHorizontal(selectionStart) + editText.getPaddingStart() + 1;
final float y = layout.getLineBaseline(line) + offsetY;

canvas.drawLine(x, y, x, y + lineHeight, paint);
}

/**
* Call on the text size of the EditText has changed when the static cursor is enabled.
*/
public void notifyTextSizeChanged() {
float textSize = editText.getTextSize();
lineHeight = editText.getLineHeight();
offsetY = offsetYBase - textSize;

// Set the stroke width (cursor width)
final DisplayMetrics displayMetrics = editText.getResources().getDisplayMetrics();
if (textSize < TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, displayMetrics)) {
paint.setStrokeWidth(2);
} else if (textSize < TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 15, displayMetrics)) {
paint.setStrokeWidth(4);
} else if (textSize < TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 25, displayMetrics)) {
paint.setStrokeWidth(5);
} else {
paint.setStrokeWidth(6);
}
}

/**
* Call on the selection of the EditText changed when the static cursor is enabled.
*
* @param selStart The new selection start location.
* @param selEnd The new selection end location.
*/
public void notifySelectionChanged(int selStart, int selEnd) {
if (selStart == selEnd) {
if (editText.isCursorVisible()) {
editText.setCursorVisible(false);
}
if (paused) {
paused = false;
}
} else if (!paused) {
paused = true; // Pause drawing the cursor when selecting text
}
}
}

public void setStaticCursorEnabled(boolean staticCursorEnabled) {
if (staticCursorEnabled) {
if (_staticCursorDrawer == null) {
_staticCursorDrawer = new StaticCursorDrawer(this, ContextCompat.getColor(getContext(), R.color.accent));
setCursorVisible(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
setTextCursorDrawable(R.drawable.cursor_transparent); // Ensure that the default cursor is invisible
}
}
} else if (_staticCursorDrawer != null) {
_staticCursorDrawer = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
setTextCursorDrawable(R.drawable.cursor_accent);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void refresh() {
}
}

public void setup(final @NonNull EditText editText) {
public void setEditText(final @NonNull EditText editText) {
if (lineNumbersEnabled) {
setLineNumbersEnabled(false);
}
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/net/gsantner/markor/model/AppSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,10 @@ public boolean isIndentWithTabKey() {
return getBool(R.string.pref_key__editor_tab_to_indent, false);
}

public boolean isStaticCursorEnabled() {
return getBool(R.string.pref_key__editor_static_cursor, false);
}

public boolean isExperimentalFeaturesEnabled() {
return getBool(R.string.pref_key__is_enable_experimental_features, BuildConfig.IS_TEST_BUILD);
}
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/res/drawable/cursor_accent.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
#########################################################*/
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size android:width="2dp" />
<solid android:color="@color/accent" />
<solid android:color="@color/accent" />
</shape>
16 changes: 16 additions & 0 deletions app/src/main/res/drawable/cursor_transparent.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?><!--
/*#######################################################
*
* Maintained 2017-2025 by Gregor Santner <gsantner AT mailbox DOT org>

*
* License: Apache 2.0
* https://github.com/gsantner/opoc/#licensing
* https://www.apache.org/licenses/LICENSE-2.0
*
#########################################################*/
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size android:width="1px" />
<solid android:color="@color/transparent" />
</shape>
25 changes: 25 additions & 0 deletions app/src/main/res/drawable/ic_input_cursor_black_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!--
~ Copyright (C) 2026 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">

<path
android:fillColor="#FF000000"
android:pathData="M8,21V19H11V5H8V3H16V5H13V19H16V21H8ZM18.05,7.05L23,12L18.05,16.95L16.636,15.535L20.172,12L16.636,8.464L18.05,7.05ZM5.95,7.05L7.364,8.464L3.828,12L7.364,15.535L5.95,16.95L1,12L5.95,7.05Z" />
</vector>
1 change: 1 addition & 0 deletions app/src/main/res/values/string-not_translatable.xml
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ work. If not, see <https://creativecommons.org/publicdomain/zero/1.0/>.
<string name="pref_key__restore_settings" translatable="false">pref_key__restore_settings</string>
<string name="pref_key__backup_settings" translatable="false">pref_key__backup_settings</string>
<string name="pref_key__editor_tab_to_indent" translatable="false">pref_key__editor_tab_to_indent</string>
<string name="pref_key__editor_static_cursor" translatable="false">pref_key__editor_static_cursor</string>
<string name="hidden_password" translatable="false">****</string>
<string name="pref_key__todotxt__additional_projects_contexts" translatable="false">"pref_key__todotxt__additional_projects_contexts"</string>
<string name="pref_key__todotxt__due_date_offset" translatable="false">pref_key__todotxt_due_date_offset</string>
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -501,5 +501,7 @@ work. If not, see <https://creativecommons.org/publicdomain/zero/1.0/>.
<string name="capitalize_words">Capitalize Words (a note→A Note)</string>
<string name="capitalize_sentences">Capitalize Sentences (a note→A note)</string>
<string name="use_tab_to_indent">Indent lines with TAB key</string>
<string name="static_cursor">Static cursor</string>
<string name="disable_cursor_blinking">Disable cursor blinking</string>
<string name="folder_local">Folder local</string>
</resources>
18 changes: 12 additions & 6 deletions app/src/main/res/xml/preferences_master.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,6 @@


<PreferenceCategory android:title="@string/features">
<CheckBoxPreference
android:defaultValue="true"
android:icon="@drawable/ic_highlight_black_24dp"
android:key="@string/pref_key__is_highlighting_activated"
android:summary="@string/highlighting_enabled_or_not__appspecific"
android:title="@string/syntax_highlighting" />
<CheckBoxPreference
android:defaultValue="false"
android:icon="@drawable/ic_widgets_black_24dp"
Expand Down Expand Up @@ -296,6 +290,12 @@
</PreferenceScreen>

<PreferenceCategory android:title="@string/syntax_highlighting">
<CheckBoxPreference
android:defaultValue="true"
android:icon="@drawable/ic_highlight_black_24dp"
android:key="@string/pref_key__is_highlighting_activated"
android:summary="@string/highlighting_enabled_or_not__appspecific"
android:title="@string/syntax_highlighting" />
<CheckBoxPreference
android:defaultValue="false"
android:icon="@drawable/ic_format_color_fill_black_24dp"
Expand Down Expand Up @@ -358,6 +358,12 @@
android:key="@string/pref_key__editor_start_editing_in_center"
android:summary="@string/edit_text_in_center_of_screen"
android:title="@string/center_text" />
<CheckBoxPreference
android:defaultValue="false"
android:icon="@drawable/ic_input_cursor_black_24dp"
android:key="@string/pref_key__editor_static_cursor"
android:summary="@string/disable_cursor_blinking"
android:title="@string/static_cursor" />
<CheckBoxPreference
android:defaultValue="false"
android:icon="@drawable/ic_vertical_align_bottom_black_24dp"
Expand Down
Loading