From 598cce7070ed9007cc1a4391e6a597ec7c025913 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Fri, 4 Feb 2022 15:41:07 +0530 Subject: [PATCH] Enhanced floating text action presenter --- .idea/misc.xml | 3 + .../main/assets/data/common/gradle-plugin.zip | Bin 37275 -> 37272 bytes .../adapters/TextActionItemAdapter.java | 16 + .../preferences/EditorPreferences.java | 438 ++++++++++-------- .../views/editor/CodeEditorView.java | 8 +- .../views/editor/EditorTextActionMode.java | 34 +- .../views/editor/EditorTextActionWindow.java | 63 ++- .../androidide/views/editor/IDEEditor.java | 35 +- app/src/main/res/drawable/ic_text_actions.xml | 8 + .../drawable/ic_text_actions_horizontal.xml | 8 + app/src/main/res/values/strings.xml | 4 + .../managers/PreferenceManager.java | 2 + .../androidide/models/ConstantsBridge.java | 1 + 13 files changed, 397 insertions(+), 223 deletions(-) create mode 100644 app/src/main/res/drawable/ic_text_actions.xml create mode 100644 app/src/main/res/drawable/ic_text_actions_horizontal.xml diff --git a/.idea/misc.xml b/.idea/misc.xml index ceb55db53..f788d142e 100755 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -21,6 +21,7 @@ + @@ -32,6 +33,8 @@ + + diff --git a/app/src/main/assets/data/common/gradle-plugin.zip b/app/src/main/assets/data/common/gradle-plugin.zip index d266fa744c4b2a2a6bc7e5e7652e393450b003e8..b0f211ef1dc385e816a0dd912ee1ca85676c40da 100644 GIT binary patch delta 1741 zcmZXUdoY55@pkj%gFsQW^x^RQ!y> zsq%(Ldebm@;x!@;wrO*)X0D2j3t)!mi+1&%|7dlb5`Mm-$0VfKp}B8S`x{DBxM*-w zvGDF>SpCMF zQhMO_NmE+2Z2t1H$M}Kr4)+w#7cv~%(5rfzM}N87vBF-9ilvVP+v>roE%swC``umU1IkJwV?%!Nn)sta|_WxuZvCHMX+V2%0z?iG+jMT@Pa10j@+pI@`XH zO5?H|?N~G?wtYhr$Bwm@wwcXhv1s=8T&^P?r@C;czoo$vEz3%DZ2MdmgAI1-Z3u!h zC8;%V+UYu;Q&ZB>=)J-nc1_*8+l`NBw-87M&M#lRR}Iy_`k*!q4dViI$UE8gG)Jwj zHbj92Q50;iWON~vIqAU2-=@5h#Oob>hMvjuEnPfK=96%nhnyEVEqX&$mPa_#h-ysy zkb{Lcppf_TddY}_N+M9%^Tq;J*}{r2-8^u+-D~GXcY@J1ZO>BKQdsQ-2x{d8 zKMf*GkO46xfJTrX@f!*`CsK%q(;LxZ2(B;_;=WJ@5gpTtLhf|@144q=QBYJo0$%f3 zfSN!6nQ^wz=UUteGPGS9jH7OyohSC1H-uaZM zdS=j;rp-SLk9jjEDboi;g8{OctL$6Pe%xqsPh>)fA&H`fShuA8evN=h zHqhen(4spxohci}Z>4Va?LBOCAvn;}=TNpTk5Rv#_gze6iNtqBkj2Grn^gn}+0rQV z)${U^Rm$^$Q=paT-!%Eb*oV<}WVd9~7X1ra^rW<~Fz)8W^0k-e>aH+prFk4vr40zthxe^sC*jEE5b z|76Qrh$&cCI{|gb`f#_NN2412|^d`vqUupx0+YoKXgC*i(7z4|6eJQi z14*Vr3C|Xt;tzB$ERQ+kpRmWjQN3?smv_vAc=21@wBaN0#%=w^rmoBmneV%A5}nAt zO5mZ9lm8bzDPM!9)k)S;xR7wDtfj`IidNAYZgc<1#GvOAW!9dAUtL{2t;0XQE7Kj| zGUZqHTi39Wte>AL?-d?A?3&K478EcY)%z>FLMtQMtZ{qs!YuB+Hf^ArVl1Caj>6tV zLF!qkFRipkGn_w@AjyM|AD)u@6sW{pcD)-+8M^o8> zjxS$j3XcT%BySKapY^)4&}c6kh{a3dGnh$Z@>S3yZhgF>JqN#60VAk#ZB$3 z=3pg|;BQ
ayoB9!m|!b0J_#?(${-=S3v8}4H;m;@{a!v(TK`hb;Y3i>iMLDT|O z!Gp4oCVS#jlaqxd`W%Xfd!bk^56}!$2-O|!pJ5x`ABwzogBWO$`{U@!(8P>8bjLwS zThUNrOv})&hTrUy)ZZ){+x*CG_oKHaWiQ8^lZfMb*p(GW9hKf$clqd6oMi0$(qub8 zCZ$L4!Y$}mvZrf|-r_n+?>mb&^p|5ei`=ewyR=_)+<&Ug^wQ!!zIQt~=0*K5(C#?V zJ-LIF$@12zAU*tR_?DfaSnbKM$GfqG=UzNi>Em(m1SJfHM8d!ws#<4#0uIEj_B0N4 zHI>2OdeG<|Y_5kj!_&r{T4zo1quEGNJ0kF{d@pDqAR`#A}*l`K{X@s3zh_Lu<9H!1a~SuG1g#WLgw!w|FD zAUCo`5LDoxde2zhW!zj|GSH|{?y7g}cW%#XJh!D&eT6kER~{T?Txm|87-iA~Ay0Sn zy0j{R>#IpgDdJX|<6z^n*RLg7l}&&0evto0|T_&Nm5Pg<@&~ZXa^7 zrqMmA94dw3L$_Uc99BCHDX;m?aA|I$WWX@l8L^rsD2CfU-okduwF$rIFQrZ&I&#RcZ z5VFxfq`3ArCZBmxTWdO}cU>c`mjYZ2Bs0P16(p*3qxnMBN2KF^fF)%6K9QkH(&NSs zzeck{W;D8!5wCt}X@PI_>NHwp?~|0XEW?F;dJRM*n4uFLeaV*4V@qUPiY&7f6 zJ^(ZCoOpfr9b4}PPz*8l(j diff --git a/app/src/main/java/com/itsaky/androidide/adapters/TextActionItemAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/TextActionItemAdapter.java index 2a204705d..d6b96588f 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/TextActionItemAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/TextActionItemAdapter.java @@ -17,13 +17,16 @@ package com.itsaky.androidide.adapters; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.databinding.LayoutTextActionItemBinding; +import com.itsaky.androidide.managers.PreferenceManager; import com.itsaky.androidide.views.editor.IDEEditor; import java.util.List; @@ -32,17 +35,20 @@ import java.util.function.Consumer; /** * Adapter for text actions in editor. + * * @author Akash Yadav */ public class TextActionItemAdapter extends RecyclerView.Adapter { private final List actions; private final Consumer onClick; + private final boolean isHorizontal; public TextActionItemAdapter (List actions, Consumer onClick) { Objects.requireNonNull (actions); this.actions = actions; this.onClick = onClick; + this.isHorizontal = StudioApp.getInstance ().getPrefManager ().getBoolean (PreferenceManager.KEY_EDITOR_HORIZONTAL_POPUP, false); } @NonNull @@ -57,6 +63,16 @@ public class TextActionItemAdapter extends RecyclerView.Adapter. * -**************************************************************************************/ + */ package com.itsaky.androidide.fragments.preferences; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_AUTO_SAVE; @@ -26,8 +26,10 @@ import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_W import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_WS_TRAILING; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FONT_LIGATURES; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FONT_SIZE; +import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_HORIZONTAL_POPUP; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_PRINTABLE_CHARS; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_TAB_SIZE; +import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_USE_POPUP; import static com.itsaky.androidide.models.PrefBasedJavaServerSettings.KEY_JAVA_PREF_MATCH_LOWER; import android.os.Bundle; @@ -49,128 +51,156 @@ import com.itsaky.androidide.utils.DialogUtils; public class EditorPreferences extends BasePreferenceFragment { @Override - public void onCreatePreferences(Bundle p1, String p2) { - super.onCreatePreferences(p1, p2); - - if(getContext() == null) { - return; - } - - final var screen = getPreferenceScreen(); - final var commonCategory = new PreferenceCategory (getContext ()); - final var javaCategory = new PreferenceCategory (getContext ()); - - final var fontSize = new Preference(getContext()); - final var nonPrintable = new Preference(getContext()); - final var tabSize = new Preference(getContext()); - final var drawHex = new SwitchPreference(getContext()); - final var fontLigatures = new SwitchPreference (getContext ()); - final var autoSave = new SwitchPreference (getContext ()); - - final var javaMatchLower = new SwitchPreference (getContext ()); - - screen.addPreference (commonCategory); - screen.addPreference (javaCategory); + public void onCreatePreferences (Bundle p1, String p2) { + super.onCreatePreferences (p1, p2); - fontSize.setIcon(R.drawable.ic_text_size); - fontSize.setKey(KEY_EDITOR_FONT_SIZE); - fontSize.setTitle(R.string.idepref_editor_fontsize_title); - fontSize.setSummary(R.string.idepref_editor_fontsize_summary); - - nonPrintable.setIcon(R.drawable.ic_drawing); - nonPrintable.setKey(KEY_EDITOR_PRINTABLE_CHARS); - nonPrintable.setTitle(R.string.idepref_editor_paintingflags_title); - nonPrintable.setSummary(R.string.idepref_editor_paintingflags_summary); + if (getContext () == null) { + return; + } - tabSize.setIcon(R.drawable.ic_tab); - tabSize.setKey(KEY_EDITOR_TAB_SIZE); - tabSize.setTitle(R.string.title_tab_size); - tabSize.setSummary(R.string.msg_tab_size); - - drawHex.setIcon(R.drawable.ic_hexadecimal); - drawHex.setKey(KEY_EDITOR_DRAW_HEX); - drawHex.setTitle(R.string.idepref_editor_drawhexcolors_title); - drawHex.setSummary(R.string.idepref_editor_drawhexcolors_summary); - - fontLigatures.setIcon(R.drawable.ic_font_ligatures); - fontLigatures.setKey(KEY_EDITOR_FONT_LIGATURES); - fontLigatures.setTitle(getString(R.string.idepref_editor_ligatures_title)); - fontLigatures.setSummary(getString(R.string.idepref_editor_ligatures_summary)); - - autoSave.setIcon (R.drawable.ic_save); - autoSave.setKey (KEY_EDITOR_AUTO_SAVE); - autoSave.setTitle (getString(R.string.idepref_editor_autoSave_title)); - autoSave.setSummary (getString(R.string.idepref_editor_autoSave_summary)); - - javaMatchLower.setKey (KEY_JAVA_PREF_MATCH_LOWER); - javaMatchLower.setIcon (R.drawable.ic_text_lower); - javaMatchLower.setTitle (getString(R.string.idepref_java_matchLower_title)); - javaMatchLower.setSummary (getString(R.string.idepref_java_matchLower_summary)); - - commonCategory.setTitle (getString(R.string.idepref_editor_category_common)); - commonCategory.addPreference(fontSize); - commonCategory.addPreference (fontLigatures); - commonCategory.addPreference(nonPrintable); - commonCategory.addPreference(tabSize); - commonCategory.addPreference(drawHex); - commonCategory.addPreference (autoSave); - - javaCategory.setTitle (getString(R.string.idepref_editor_category_java)); - javaCategory.addPreference (javaMatchLower); - - setPreferenceScreen(screen); - - fontSize.setOnPreferenceClickListener(this::onPreferenceClick); - fontLigatures.setOnPreferenceChangeListener (this::onPreferenceChange); - nonPrintable.setOnPreferenceClickListener(this::onPreferenceClick); - tabSize.setOnPreferenceClickListener(this::onPreferenceClick); - drawHex.setOnPreferenceChangeListener(this::onPreferenceChange); - autoSave.setOnPreferenceChangeListener (this::onPreferenceChange); - javaMatchLower.setOnPreferenceChangeListener (this::onPreferenceChange); - - fontLigatures.setChecked (getPrefManager ().getBoolean (KEY_EDITOR_FONT_LIGATURES, true)); - drawHex.setChecked(getPrefManager().getBoolean(KEY_EDITOR_DRAW_HEX, true)); - autoSave.setChecked (getPrefManager ().getBoolean (KEY_EDITOR_AUTO_SAVE, false)); - javaMatchLower.setChecked (getPrefManager ().getBoolean (KEY_JAVA_PREF_MATCH_LOWER, false)); - } - - public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) { - final boolean value = (boolean) newValue; - - switch (preference.getKey ()) { - case KEY_EDITOR_DRAW_HEX: - ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED = true; - break; - case KEY_EDITOR_FONT_LIGATURES: - ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED = true; - break; - case KEY_JAVA_PREF_MATCH_LOWER: - final var javaServer = StudioApp.getInstance ().getJavaLanguageServer (); - javaServer.applySettings (PrefBasedJavaServerSettings.getInstance ()); - break; - } - - getPrefManager ().putBoolean (preference.getKey (), value); - return true; - } - - public boolean onPreferenceClick(@NonNull Preference preference) { - final String key = preference.getKey(); - switch (key) { - case KEY_EDITOR_FONT_SIZE: - showTextSizeDialog (); - break; - case KEY_EDITOR_PRINTABLE_CHARS: - showPrintableCharsDialog (); - break; - case KEY_EDITOR_TAB_SIZE: - showTabSizeDialog (); - break; - } - return true; - } - - private void showTabSizeDialog() { + final var screen = getPreferenceScreen (); + final var commonCategory = new PreferenceCategory (getContext ()); + final var javaCategory = new PreferenceCategory (getContext ()); + + final var fontSize = new Preference (getContext ()); + final var nonPrintable = new Preference (getContext ()); + final var tabSize = new Preference (getContext ()); + final var drawHex = new SwitchPreference (getContext ()); + final var fontLigatures = new SwitchPreference (getContext ()); + final var autoSave = new SwitchPreference (getContext ()); + final var usePopupActions = new SwitchPreference (getContext ()); + final var horizontalPopup = new SwitchPreference (getContext ()); + + final var javaMatchLower = new SwitchPreference (getContext ()); + + screen.addPreference (commonCategory); + screen.addPreference (javaCategory); + + fontSize.setIcon (R.drawable.ic_text_size); + fontSize.setKey (KEY_EDITOR_FONT_SIZE); + fontSize.setTitle (R.string.idepref_editor_fontsize_title); + fontSize.setSummary (R.string.idepref_editor_fontsize_summary); + + nonPrintable.setIcon (R.drawable.ic_drawing); + nonPrintable.setKey (KEY_EDITOR_PRINTABLE_CHARS); + nonPrintable.setTitle (R.string.idepref_editor_paintingflags_title); + nonPrintable.setSummary (R.string.idepref_editor_paintingflags_summary); + + tabSize.setIcon (R.drawable.ic_tab); + tabSize.setKey (KEY_EDITOR_TAB_SIZE); + tabSize.setTitle (R.string.title_tab_size); + tabSize.setSummary (R.string.msg_tab_size); + + drawHex.setIcon (R.drawable.ic_hexadecimal); + drawHex.setKey (KEY_EDITOR_DRAW_HEX); + drawHex.setTitle (R.string.idepref_editor_drawhexcolors_title); + drawHex.setSummary (R.string.idepref_editor_drawhexcolors_summary); + + fontLigatures.setIcon (R.drawable.ic_font_ligatures); + fontLigatures.setKey (KEY_EDITOR_FONT_LIGATURES); + fontLigatures.setTitle (getString (R.string.idepref_editor_ligatures_title)); + fontLigatures.setSummary (getString (R.string.idepref_editor_ligatures_summary)); + + usePopupActions.setIcon (R.drawable.ic_text_actions); + usePopupActions.setKey (KEY_EDITOR_USE_POPUP); + usePopupActions.setTitle (getString (R.string.title_use_popup_actions)); + usePopupActions.setSummary (getString (R.string.msg_use_popup_actions)); + + horizontalPopup.setIcon (R.drawable.ic_text_actions_horizontal); + horizontalPopup.setKey (KEY_EDITOR_HORIZONTAL_POPUP); + horizontalPopup.setTitle (getString (R.string.title_use_horizontal_popup)); + horizontalPopup.setSummary (getString (R.string.msg_use_horizontal_popup)); + + autoSave.setIcon (R.drawable.ic_save); + autoSave.setKey (KEY_EDITOR_AUTO_SAVE); + autoSave.setTitle (getString (R.string.idepref_editor_autoSave_title)); + autoSave.setSummary (getString (R.string.idepref_editor_autoSave_summary)); + + javaMatchLower.setKey (KEY_JAVA_PREF_MATCH_LOWER); + javaMatchLower.setIcon (R.drawable.ic_text_lower); + javaMatchLower.setTitle (getString (R.string.idepref_java_matchLower_title)); + javaMatchLower.setSummary (getString (R.string.idepref_java_matchLower_summary)); + + commonCategory.setTitle (getString (R.string.idepref_editor_category_common)); + commonCategory.addPreference (fontSize); + commonCategory.addPreference (fontLigatures); + commonCategory.addPreference (usePopupActions); + commonCategory.addPreference (horizontalPopup); + commonCategory.addPreference (nonPrintable); + commonCategory.addPreference (tabSize); + commonCategory.addPreference (drawHex); + commonCategory.addPreference (autoSave); + + javaCategory.setTitle (getString (R.string.idepref_editor_category_java)); + javaCategory.addPreference (javaMatchLower); + + setPreferenceScreen (screen); + + fontSize.setOnPreferenceClickListener (this::onPreferenceClick); + fontLigatures.setOnPreferenceChangeListener (this::onPreferenceChange); + usePopupActions.setOnPreferenceChangeListener (this::onPreferenceChange); + horizontalPopup.setOnPreferenceChangeListener (this::onPreferenceChange); + nonPrintable.setOnPreferenceClickListener (this::onPreferenceClick); + tabSize.setOnPreferenceClickListener (this::onPreferenceClick); + drawHex.setOnPreferenceChangeListener (this::onPreferenceChange); + autoSave.setOnPreferenceChangeListener (this::onPreferenceChange); + javaMatchLower.setOnPreferenceChangeListener (this::onPreferenceChange); + + fontLigatures.setChecked (getPrefManager ().getBoolean (KEY_EDITOR_FONT_LIGATURES, true)); + usePopupActions.setChecked (getPrefManager ().getBoolean (KEY_EDITOR_USE_POPUP, false)); + horizontalPopup.setChecked (getPrefManager ().getBoolean (KEY_EDITOR_HORIZONTAL_POPUP, false)); + drawHex.setChecked (getPrefManager ().getBoolean (KEY_EDITOR_DRAW_HEX, true)); + autoSave.setChecked (getPrefManager ().getBoolean (KEY_EDITOR_AUTO_SAVE, false)); + javaMatchLower.setChecked (getPrefManager ().getBoolean (KEY_JAVA_PREF_MATCH_LOWER, false)); + } + + public boolean onPreferenceChange (@NonNull Preference preference, Object newValue) { + final boolean value = (boolean) newValue; + + switch (preference.getKey ()) { + case KEY_EDITOR_DRAW_HEX: + ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED = true; + break; + case KEY_EDITOR_FONT_LIGATURES: + ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED = true; + break; + case KEY_EDITOR_USE_POPUP: + case KEY_EDITOR_HORIZONTAL_POPUP: + ConstantsBridge.EDITOR_TEXT_ACTIONS_CHANGED = true; + break; + case KEY_JAVA_PREF_MATCH_LOWER: + final var javaServer = StudioApp.getInstance ().getJavaLanguageServer (); + javaServer.applySettings (PrefBasedJavaServerSettings.getInstance ()); + break; + } + + getPrefManager ().putBoolean (preference.getKey (), value); + + final var horizontalPopup = findPreference (KEY_EDITOR_HORIZONTAL_POPUP); + if (horizontalPopup != null) { + horizontalPopup.setEnabled (getPrefManager ().getBoolean (KEY_EDITOR_USE_POPUP)); + } + + return true; + } + + public boolean onPreferenceClick (@NonNull Preference preference) { + final String key = preference.getKey (); + switch (key) { + case KEY_EDITOR_FONT_SIZE: + showTextSizeDialog (); + break; + case KEY_EDITOR_PRINTABLE_CHARS: + showPrintableCharsDialog (); + break; + case KEY_EDITOR_TAB_SIZE: + showTabSizeDialog (); + break; + } + return true; + } + + private void showTabSizeDialog () { final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (getContext ()); final String[] sizes = new String[]{"2", "4", "6", "8"}; @@ -180,85 +210,89 @@ public class EditorPreferences extends BasePreferenceFragment { // Like, 2, 4, 6, 8, 10, etc // // If current tab size if 4, 4/2 = 2 and 2 - 1 = 1, so option at index 1 will be selected which is current tab size (4)! - int current = (getPrefManager().getEditorTabSize() / 2) - 1; - if(current < 0 || current >= sizes.length) current = 1; - builder.setTitle(R.string.title_tab_size); - builder.setSingleChoiceItems(sizes, current, (d, i) -> { - d.dismiss(); + int current = (getPrefManager ().getEditorTabSize () / 2) - 1; + if (current < 0 || current >= sizes.length) { + current = 1; + } + builder.setTitle (R.string.title_tab_size); + builder.setSingleChoiceItems (sizes, current, (d, i) -> { + d.dismiss (); - // Reversing the logic applied above... + // Reversing the logic applied above... int tabSize = (i + 1) * 2; - if(tabSize < 2 || tabSize > 8) tabSize = 4; - getPrefManager().putInt(KEY_EDITOR_TAB_SIZE, tabSize); + if (tabSize < 2 || tabSize > 8) { + tabSize = 4; + } + getPrefManager ().putInt (KEY_EDITOR_TAB_SIZE, tabSize); }); - builder.setCancelable(true); - builder.show(); + builder.setCancelable (true); + builder.show (); + } + + private void showTextSizeDialog () { + final LayoutTextSizeSliderBinding binding = LayoutTextSizeSliderBinding.inflate (LayoutInflater.from (getContext ())); + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (getContext ()); + float size = getPrefManager ().getFloat (KEY_EDITOR_FONT_SIZE, 14); + if (size < 6 || size > 32) { + size = 14; + } + changeTextSize (binding, size); + + binding.slider.setLabelFormatter (p1 -> String.valueOf ((int) p1)); + builder.setTitle (R.string.title_change_text_size); + builder.setMessage (R.string.msg_editor_font_size); + builder.setView (binding.getRoot ()); + builder.setPositiveButton (android.R.string.ok, (p1, p2) -> { + p1.dismiss (); + float s = binding.slider.getValue (); + changeTextSize (binding, s); + }); + builder.setNeutralButton (R.string.reset, (p1, p2) -> changeTextSize (binding, 14)); + builder.setCancelable (false); + builder.show (); + } + + private void changeTextSize (LayoutTextSizeSliderBinding binding, float size) { + getPrefManager ().putFloat (KEY_EDITOR_FONT_SIZE, size); + binding.slider.setValue (size); + ConstantsBridge.EDITOR_PREF_SIZE_CHANGED = true; + } + + private void showPrintableCharsDialog () { + String[] labels = { + "Leading whitespaces", + "Trailing whitespaces", + "Inner whitespaces", + "Whitespaces in empty lines", + "Line breaks" + }; + final boolean[] checked = { + getPrefManager ().getBoolean (KEY_EDITOR_FLAG_WS_LEADING, true), + getPrefManager ().getBoolean (KEY_EDITOR_FLAG_WS_TRAILING, false), + getPrefManager ().getBoolean (KEY_EDITOR_FLAG_WS_INNER, true), + getPrefManager ().getBoolean (KEY_EDITOR_FLAG_WS_EMPTY_LINE, true), + getPrefManager ().getBoolean (KEY_EDITOR_FLAG_LINE_BREAK, true) + }; + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (getContext ()); + builder.setTitle (R.string.idepref_editor_paintingflags_title); + builder.setMultiChoiceItems (labels, checked, (dialog, which, isChecked) -> { + + if (which == 0) { + getPrefManager ().putBoolean (KEY_EDITOR_FLAG_WS_LEADING, isChecked); + } else if (which == 1) { + getPrefManager ().putBoolean (KEY_EDITOR_FLAG_WS_TRAILING, isChecked); + } else if (which == 2) { + getPrefManager ().putBoolean (KEY_EDITOR_FLAG_WS_INNER, isChecked); + } else if (which == 3) { + getPrefManager ().putBoolean (KEY_EDITOR_FLAG_WS_EMPTY_LINE, isChecked); + } else if (which == 4) { + getPrefManager ().putBoolean (KEY_EDITOR_FLAG_LINE_BREAK, isChecked); + } + + ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = true; + }); + builder.setPositiveButton (android.R.string.ok, null); + builder.setCancelable (false); + builder.show (); } - - private void showTextSizeDialog() { - final LayoutTextSizeSliderBinding binding = LayoutTextSizeSliderBinding.inflate(LayoutInflater.from(getContext())); - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (getContext ()); - float size = getPrefManager().getFloat(KEY_EDITOR_FONT_SIZE, 14); - if(size < 6 || size > 32) { - size = 14; - } - changeTextSize(binding, size); - - binding.slider.setLabelFormatter(p1 -> String.valueOf((int) p1)); - builder.setTitle(R.string.title_change_text_size); - builder.setMessage(R.string.msg_editor_font_size); - builder.setView(binding.getRoot()); - builder.setPositiveButton(android.R.string.ok, (p1, p2) -> { - p1.dismiss(); - float s = binding.slider.getValue(); - changeTextSize(binding, s); - }); - builder.setNeutralButton(R.string.reset, (p1, p2) -> changeTextSize(binding, 14)); - builder.setCancelable(false); - builder.show(); - } - - private void changeTextSize(LayoutTextSizeSliderBinding binding, float size) { - getPrefManager().putFloat(KEY_EDITOR_FONT_SIZE, size); - binding.slider.setValue(size); - ConstantsBridge.EDITOR_PREF_SIZE_CHANGED = true; - } - - private void showPrintableCharsDialog() { - String[] labels = { - "Leading whitespaces", - "Trailing whitespaces", - "Inner whitespaces", - "Whitespaces in empty lines", - "Line breaks" - }; - final boolean[] checked = { - getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_LEADING, true), - getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_TRAILING, false), - getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_INNER, true), - getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, true), - getPrefManager().getBoolean(KEY_EDITOR_FLAG_LINE_BREAK, true) - }; - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (getContext ()); - builder.setTitle(R.string.idepref_editor_paintingflags_title); - builder.setMultiChoiceItems(labels, checked, (dialog, which, isChecked) -> { - - if(which == 0) { - getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_LEADING, isChecked); - } else if(which == 1) { - getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_TRAILING, isChecked); - } else if(which == 2) { - getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_INNER, isChecked); - } else if(which == 3) { - getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, isChecked); - } else if(which == 4) { - getPrefManager().putBoolean(KEY_EDITOR_FLAG_LINE_BREAK, isChecked); - } - - ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = true; - }); - builder.setPositiveButton(android.R.string.ok, null); - builder.setCancelable(false); - builder.show(); - } } \ No newline at end of file diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java b/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java index 1f3e4034d..a9d20a5ed 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java @@ -72,7 +72,6 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import io.github.rosemoe.sora.event.ContentChangeEvent; -import io.github.rosemoe.sora.event.SelectionChangeEvent; import io.github.rosemoe.sora.lang.EmptyLanguage; import io.github.rosemoe.sora.text.Content; @@ -290,6 +289,7 @@ public class CodeEditorView extends FrameLayout { private void configureEditorIfNeeded () { boolean sizeChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_SIZE_CHANGED; boolean ligaturesChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED; + boolean popupChanged = isFirstCreate || ConstantsBridge.EDITOR_TEXT_ACTIONS_CHANGED; boolean flagsChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED; boolean drawHexChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED; final PreferenceManager prefs = StudioApp.getInstance ().getPrefManager (); @@ -307,6 +307,7 @@ public class CodeEditorView extends FrameLayout { if (ligaturesChanged) { var enabled = prefs.getBoolean (PreferenceManager.KEY_EDITOR_FONT_LIGATURES, true); binding.editor.setLigatureEnabled (enabled); + ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED = false; } if (flagsChanged) { @@ -335,6 +336,11 @@ public class CodeEditorView extends FrameLayout { ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = false; } + if (popupChanged) { + binding.editor.setTextActionPresenter (binding.editor.chooseTextActionPresenter ()); + ConstantsBridge.EDITOR_TEXT_ACTIONS_CHANGED = false; + } + if (drawHexChanged) { // TODO Do something about this // Maybe use spans... diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionMode.java b/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionMode.java index f3b3cf336..55199a388 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionMode.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionMode.java @@ -32,7 +32,6 @@ import java.util.Set; import java.util.TreeSet; import io.github.rosemoe.sora.event.ClickEvent; -import io.github.rosemoe.sora.event.LongPressEvent; import io.github.rosemoe.sora.event.SelectionChangeEvent; import io.github.rosemoe.sora.widget.component.EditorTextActionWindow; @@ -45,6 +44,7 @@ public class EditorTextActionMode implements IDEEditor.ITextActionPresenter { private IDEEditor editor; private ActionMode actionMode; + private boolean unsubscribeEvents = false; private final Set registeredActions = new TreeSet<> (); @@ -60,6 +60,12 @@ public class EditorTextActionMode implements IDEEditor.ITextActionPresenter { this.editor.getComponent (EditorTextActionWindow.class).setEnabled (false); this.editor.subscribeEvent (SelectionChangeEvent.class, ((event, unsubscribe) -> { + + if (unsubscribeEvents) { + unsubscribe.unsubscribe (); + return; + } + final var left = event.getLeft (); final var right = event.getRight (); if (left.index != right.index) { @@ -68,13 +74,14 @@ public class EditorTextActionMode implements IDEEditor.ITextActionPresenter { })); // When click event is received, finish the action mode if it is started - this.editor.subscribeEvent (ClickEvent.class, (event, unsubscribe) -> exit ()); - - // Start action mode even if no text is selected - this.editor.subscribeEvent (LongPressEvent.class, (event, unsubscribe) -> { - if (actionMode == null) { - showAction (); + this.editor.subscribeEvent (ClickEvent.class, (event, unsubscribe) -> { + + if (unsubscribeEvents) { + unsubscribe.unsubscribe (); + return; } + + exit (); }); clearRegisteredActions (); @@ -87,6 +94,19 @@ public class EditorTextActionMode implements IDEEditor.ITextActionPresenter { this.registeredActions.add (action); } + @Override + public void destroy () { + + if (actionMode != null) { + exit (); + } + + this.registeredActions.clear (); + this.editor = null; + this.actionMode = null; + this.unsubscribeEvents = true; + } + public void clearRegisteredActions () { this.registeredActions.clear (); } diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionWindow.java b/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionWindow.java index a2d297a1a..b47aab837 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionWindow.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionWindow.java @@ -29,6 +29,8 @@ import androidx.recyclerview.widget.RecyclerView; import com.itsaky.androidide.R; import com.itsaky.androidide.adapters.TextActionItemAdapter; +import com.itsaky.androidide.app.StudioApp; +import com.itsaky.androidide.managers.PreferenceManager; import java.util.Objects; import java.util.Set; @@ -57,6 +59,7 @@ public class EditorTextActionWindow extends EditorPopupWindow implements IDEEdit private final static long DELAY = 200; private long mLastScroll; private int mLastPosition; + private boolean unsubscribeEvents = false; private final Set registeredActions = new TreeSet<> (); @@ -80,7 +83,6 @@ public class EditorTextActionWindow extends EditorPopupWindow implements IDEEdit this.touchHandler = editor.getEventHandler (); this.actionsList = new RecyclerView (editor.getContext ()); this.actionsList.setLayoutParams (new ViewGroup.LayoutParams (ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - this.actionsList.setLayoutManager (new LinearLayoutManager (editor.getContext ())); this.actionsList.setBackground (createBackground ()); this.actionsList.setVerticalFadingEdgeEnabled (true); this.actionsList.setFadingEdgeLength ((int) (10 * editor.getDpUnit ())); @@ -89,10 +91,41 @@ public class EditorTextActionWindow extends EditorPopupWindow implements IDEEdit setContentView (this.actionsList); subscribeToEvents (); + applyLayoutManager (); this.registeredActions.clear (); } + @Override + public void registerAction (@NonNull IDEEditor.TextAction action) { + Objects.requireNonNull (editor, "No editor attached!"); + + this.registeredActions.add (action); + } + + @Override + public void destroy () { + + if (isShowing ()) { + dismiss (); + } + + this.registeredActions.clear (); + this.editor = null; + this.actionsList = null; + this.unsubscribeEvents = true; + } + + private void applyLayoutManager () { + final var manager = StudioApp.getInstance ().getPrefManager (); + final var horizontal = manager.getBoolean (PreferenceManager.KEY_EDITOR_HORIZONTAL_POPUP, false); + if (horizontal) { + this.actionsList.setLayoutManager (new LinearLayoutManager (this.actionsList.getContext (), LinearLayoutManager.HORIZONTAL, false)); + } else { + this.actionsList.setLayoutManager (new LinearLayoutManager (this.actionsList.getContext ())); + } + } + @NonNull private Drawable createBackground () { final var drawable = new GradientDrawable (); @@ -102,28 +135,22 @@ public class EditorTextActionWindow extends EditorPopupWindow implements IDEEdit return drawable; } - @Override - public void registerAction (@NonNull IDEEditor.TextAction action) { - Objects.requireNonNull (editor, "No editor attached!"); - - this.registeredActions.add (action); - } - @Override public void show () { Objects.requireNonNull (editor, "No editor attached!"); + final var dp16 = editor.getDpUnit () * 16; final var actions = this.registeredActions.stream () .filter (action -> this.editor.shouldShowTextAction (action.id)) .collect (Collectors.toList ()); this.actionsList.setAdapter (new TextActionItemAdapter (actions, this::performTextAction)); this.actionsList.measure ( View.MeasureSpec.makeMeasureSpec ( - editor.getWidth (), + (int) (editor.getWidth () - dp16 * 2), // 16dp margins from start and end View.MeasureSpec.AT_MOST ), View.MeasureSpec.makeMeasureSpec ( - (int) (260 * editor.getDpUnit ()), // 260dp at most + (int) ((int) (260 * editor.getDpUnit ()) - dp16 * 2), // 260dp at most and 16dp margins from top and bottom View.MeasureSpec.AT_MOST ) ); @@ -134,6 +161,11 @@ public class EditorTextActionWindow extends EditorPopupWindow implements IDEEdit private void subscribeToEvents () { this.editor.subscribeEvent (SelectionChangeEvent.class, (event, unsubscribe) -> { + if (unsubscribeEvents) { + unsubscribe.unsubscribe (); + return; + } + if (touchHandler.hasAnyHeldHandle ()) { return; } @@ -160,6 +192,11 @@ public class EditorTextActionWindow extends EditorPopupWindow implements IDEEdit } }); this.editor.subscribeEvent (ScrollEvent.class, ((event, unsubscribe) -> { + if (unsubscribeEvents) { + unsubscribe.unsubscribe (); + return; + } + var last = mLastScroll; mLastScroll = System.currentTimeMillis (); if (mLastScroll - last < DELAY) { @@ -167,6 +204,12 @@ public class EditorTextActionWindow extends EditorPopupWindow implements IDEEdit } })); this.editor.subscribeEvent (HandleStateChangeEvent.class, ((event, unsubscribe) -> { + + if (unsubscribeEvents) { + unsubscribe.unsubscribe (); + return; + } + if (event.isHeld ()) { postDisplay (); } diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java b/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java index 40aeb0612..81e266aac 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java @@ -37,6 +37,7 @@ import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.language.IDELanguage; import com.itsaky.androidide.lsp.IDELanguageClientImpl; +import com.itsaky.androidide.managers.PreferenceManager; import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE; import com.itsaky.androidide.utils.Logger; import com.itsaky.lsp.api.ILanguageServer; @@ -58,6 +59,8 @@ import com.itsaky.lsp.models.SignatureHelp; import com.itsaky.lsp.models.SignatureHelpParams; import com.itsaky.toaster.Toaster; +import org.jetbrains.annotations.Contract; + import java.io.File; import java.util.List; import java.util.Objects; @@ -98,7 +101,7 @@ public class IDEEditor extends CodeEditor { super (context, attrs, defStyleAttr, defStyleRes); setColorScheme (new SchemeAndroidIDE ()); - setTextActionPresenter (new EditorTextActionWindow (this)); + setTextActionPresenter (chooseTextActionPresenter ()); subscribeEvent (SelectionChangeEvent.class, (event, unsubscribe) -> handleSelectionChange (event)); subscribeEvent (ContentChangeEvent.class, (event, unsubscribe) -> handleContentChange (event)); @@ -109,6 +112,18 @@ public class IDEEditor extends CodeEditor { | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); } + @NonNull + @Contract(" -> new") + public ITextActionPresenter chooseTextActionPresenter () { + final var prefs = StudioApp.getInstance ().getPrefManager (); + final var usePopup = prefs.getBoolean (PreferenceManager.KEY_EDITOR_USE_POPUP, false); + if (usePopup) { + return new EditorTextActionWindow (this); + } else { + return new EditorTextActionMode (); + } + } + /** * Set selection to the given range. * @@ -567,6 +582,7 @@ public class IDEEditor extends CodeEditor { * * @return The {@link Position} of the cursor. */ + @SuppressWarnings("unused") public Position getCursorAsLSPPosition () { return new Position (getCursor ().getLeftLine (), getCursor ().getLeftColumn ()); } @@ -590,6 +606,12 @@ public class IDEEditor extends CodeEditor { */ public void setTextActionPresenter (@NonNull ITextActionPresenter actionPresenter) { Objects.requireNonNull (actionPresenter, "Cannot set text action presenter to null"); + + if (mTextActionPresenter != null) { + mTextActionPresenter.destroy (); + mTextActionPresenter = null; + } + this.mTextActionPresenter = actionPresenter; actionPresenter.bindEditor (this); @@ -601,6 +623,7 @@ public class IDEEditor extends CodeEditor { * * @return The attached text action presenter. */ + @SuppressWarnings("unused") public ITextActionPresenter getTextActionPresenter () { return mTextActionPresenter; } @@ -830,7 +853,7 @@ public class IDEEditor extends CodeEditor { * Notify the language server that the content of this file * has been changed. * - * @param event + * @param event The content change event. */ private void handleContentChange (ContentChangeEvent event) { @@ -903,7 +926,7 @@ public class IDEEditor extends CodeEditor { /** * Bind the action presenter with the given editor instance. * - * @param editor + * @param editor The editor to bind with. */ void bindEditor (@NonNull IDEEditor editor); @@ -913,6 +936,12 @@ public class IDEEditor extends CodeEditor { * @param action The action to register. */ void registerAction (@NonNull TextAction action); + + /** + * Destroy this action presenter. The presenter should unsubscribe + * from any subscribed events and release any held resources. + */ + void destroy (); } /** diff --git a/app/src/main/res/drawable/ic_text_actions.xml b/app/src/main/res/drawable/ic_text_actions.xml new file mode 100644 index 000000000..1de5f6515 --- /dev/null +++ b/app/src/main/res/drawable/ic_text_actions.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_text_actions_horizontal.xml b/app/src/main/res/drawable/ic_text_actions_horizontal.xml new file mode 100644 index 000000000..4b66cb835 --- /dev/null +++ b/app/src/main/res/drawable/ic_text_actions_horizontal.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 24f5ea8e4..5904abd4d 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -370,4 +370,8 @@ Yes No + Floating text actions + Use PopupWindow for showing text actions + Horizontal text actions + Make the floating text actions appear horizontally. diff --git a/common/src/main/java/com/itsaky/androidide/managers/PreferenceManager.java b/common/src/main/java/com/itsaky/androidide/managers/PreferenceManager.java index 5a573aa5b..522be325c 100755 --- a/common/src/main/java/com/itsaky/androidide/managers/PreferenceManager.java +++ b/common/src/main/java/com/itsaky/androidide/managers/PreferenceManager.java @@ -44,6 +44,8 @@ public class PreferenceManager { public static final String KEY_EDITOR_TAB_SIZE = "idepref_editor_tabSize"; public static final String KEY_EDITOR_AUTO_SAVE = "idepref_editor_autoSave"; public static final String KEY_EDITOR_FONT_LIGATURES = "idepref_editor_fontLigatures"; + public static final String KEY_EDITOR_USE_POPUP = "idepref_editor_usePopup"; + public static final String KEY_EDITOR_HORIZONTAL_POPUP = "idepref_editor_horizontalPopup"; public static final String KEY_GRADLE_CMD_STACK_TRACE = "idepref_gradleCmd_stacktrace"; public static final String KEY_GRADLE_CMD_DEBUG = "idepref_gradleCmd_debug"; diff --git a/common/src/main/java/com/itsaky/androidide/models/ConstantsBridge.java b/common/src/main/java/com/itsaky/androidide/models/ConstantsBridge.java index 57239489c..16f3f440f 100755 --- a/common/src/main/java/com/itsaky/androidide/models/ConstantsBridge.java +++ b/common/src/main/java/com/itsaky/androidide/models/ConstantsBridge.java @@ -21,6 +21,7 @@ public class ConstantsBridge { public static boolean EDITOR_PREF_SIZE_CHANGED = false; public static boolean EDITOR_PREF_LIGATURES_CHANGED = false; + public static boolean EDITOR_TEXT_ACTIONS_CHANGED = false; public static boolean EDITOR_PREF_FLAGS_CHANGED = false; public static boolean EDITOR_PREF_DRAW_HEX_CHANGED = false; public static boolean CLASS_LOAD_SUCCESS = true;