diff --git a/app/src/main/java/com/termux/window/FloatingBubbleManager.java b/app/src/main/java/com/termux/window/FloatingBubbleManager.java
new file mode 100644
index 0000000..11e0ce3
--- /dev/null
+++ b/app/src/main/java/com/termux/window/FloatingBubbleManager.java
@@ -0,0 +1,135 @@
+package com.termux.window;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.termux.view.TerminalView;
+
+/**
+ * Handles displaying our TermuxFloatView as a collapsed bubble and restoring back
+ * to its original display.
+ */
+public class FloatingBubbleManager {
+ private static final int BUBBLE_SIZE = 200;
+
+ private TermuxFloatView mTermuxFloatView;
+
+ private boolean mIsMinimized;
+
+ // preserve original layout values so we can restore to normal window
+ // from our bubble
+ private int mOriginalLayoutWidth;
+ private int mOriginalLayoutHeight;
+ private boolean mDidCaptureOriginalValues;
+ private Drawable mOriginalTerminalViewBackground;
+ private Drawable mOriginalFloatViewBackground;
+
+
+ public FloatingBubbleManager(TermuxFloatView termuxFloatView) {
+ mTermuxFloatView = termuxFloatView;
+ }
+
+ public void toggleBubble() {
+ if (isMinimized()) {
+ displayAsFloatingWindow();
+ } else {
+ displayAsFloatingBubble();
+ }
+ }
+
+ public void updateLongPressBackgroundResource(boolean isInLongPressState) {
+ if (isMinimized()) {
+ return;
+ }
+ mTermuxFloatView.setBackgroundResource(isInLongPressState ? R.drawable.floating_window_background_resize : R.drawable.floating_window_background);
+ }
+
+ public void displayAsFloatingBubble() {
+ captureOriginalLayoutValues();
+
+ WindowManager.LayoutParams layoutParams = getLayoutParams();
+ layoutParams.width = BUBBLE_SIZE;
+ layoutParams.height = BUBBLE_SIZE;
+
+ TerminalView terminalView = getTerminalView();
+ terminalView.setBackgroundResource(R.drawable.round_button);
+ terminalView.setClipToOutline(true);
+
+ TermuxFloatView termuxFloatView = getTermuxFloatView();
+ termuxFloatView.setBackgroundResource(R.drawable.round_button_with_outline);
+ termuxFloatView.setClipToOutline(true);
+ termuxFloatView.hideTouchKeyboard();
+ termuxFloatView.changeFocus(false);
+
+ ViewGroup windowControls = termuxFloatView.findViewById(R.id.window_controls);
+ windowControls.setVisibility(View.GONE);
+
+ getWindowManager().updateViewLayout(termuxFloatView, layoutParams);
+ mIsMinimized = true;
+ }
+
+ public void displayAsFloatingWindow() {
+ WindowManager.LayoutParams layoutParams = getLayoutParams();
+
+ // restore back to previous values
+ layoutParams.width = mOriginalLayoutWidth;
+ layoutParams.height = mOriginalLayoutHeight;
+
+ TerminalView terminalView = getTerminalView();
+ terminalView.setBackground(mOriginalTerminalViewBackground);
+ terminalView.setClipToOutline(false);
+
+ TermuxFloatView termuxFloatView = getTermuxFloatView();
+ termuxFloatView.setBackground(mOriginalFloatViewBackground);
+ termuxFloatView.setClipToOutline(false);
+
+ ViewGroup windowControls = termuxFloatView.findViewById(R.id.window_controls);
+ windowControls.setVisibility(View.VISIBLE);
+
+ getWindowManager().updateViewLayout(termuxFloatView, layoutParams);
+ mIsMinimized = false;
+
+ // clear so we can capture proper values on next minimize
+ mDidCaptureOriginalValues = false;
+ }
+
+ public boolean isMinimized() {
+ return mIsMinimized;
+ }
+
+ private void captureOriginalLayoutValues() {
+ if (!mDidCaptureOriginalValues) {
+ WindowManager.LayoutParams layoutParams = getLayoutParams();
+ mOriginalLayoutWidth = layoutParams.width;
+ mOriginalLayoutHeight = layoutParams.height;
+
+ mOriginalTerminalViewBackground = getTerminalView().getBackground();
+ mOriginalFloatViewBackground = getTermuxFloatView().getBackground();
+ mDidCaptureOriginalValues = true;
+ }
+ }
+
+ public void cleanup() {
+ mTermuxFloatView = null;
+ mOriginalFloatViewBackground = null;
+ mOriginalTerminalViewBackground = null;
+ }
+
+ private TermuxFloatView getTermuxFloatView() {
+ return mTermuxFloatView;
+ }
+
+ private TerminalView getTerminalView() {
+ return mTermuxFloatView.mTerminalView;
+ }
+
+ private WindowManager getWindowManager() {
+ return mTermuxFloatView.mWindowManager;
+ }
+
+ private WindowManager.LayoutParams getLayoutParams() {
+ return (WindowManager.LayoutParams) mTermuxFloatView.getLayoutParams();
+ }
+}
diff --git a/app/src/main/java/com/termux/window/TermuxFloatView.java b/app/src/main/java/com/termux/window/TermuxFloatView.java
index 88da566..5b0f4df 100644
--- a/app/src/main/java/com/termux/window/TermuxFloatView.java
+++ b/app/src/main/java/com/termux/window/TermuxFloatView.java
@@ -2,6 +2,7 @@
import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Typeface;
@@ -12,8 +13,11 @@
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
+import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
@@ -35,6 +39,8 @@ public class TermuxFloatView extends LinearLayout {
InputMethodManager imm;
TerminalView mTerminalView;
+ ViewGroup mWindowControls;
+ FloatingBubbleManager mFloatingBubbleManager;
private boolean withFocus = true;
int initialX;
@@ -46,6 +52,8 @@ public class TermuxFloatView extends LinearLayout {
final int[] location = new int[2];
+ final int[] windowControlsLocation = new int[2];
+
final ScaleGestureDetector mScaleDetector = new ScaleGestureDetector(getContext(), new OnScaleGestureListener() {
private static final int MIN_SIZE = 50;
@@ -89,6 +97,19 @@ private static int computeLayoutFlags(boolean withFocus) {
public void initializeFloatingWindow() {
mTerminalView = findViewById(R.id.terminal_view);
mTerminalView.setOnKeyListener(new TermuxFloatViewClient(this));
+ mFloatingBubbleManager = new FloatingBubbleManager(this);
+ initWindowControls();
+ }
+
+ private void initWindowControls() {
+ mWindowControls = findViewById(R.id.window_controls);
+ mWindowControls.setOnClickListener(v -> changeFocus(true));
+
+ Button minimizeButton = findViewById(R.id.minimize_button);
+ minimizeButton.setOnClickListener(v -> mFloatingBubbleManager.toggleBubble());
+
+ Button exitButton = findViewById(R.id.exit_button);
+ exitButton.setOnClickListener(v -> exit());
}
@Override
@@ -147,6 +168,13 @@ public boolean onInterceptTouchEvent(MotionEvent event) {
int y = location[1];
float touchX = event.getRawX();
float touchY = event.getRawY();
+
+ if (didClickInsideWindowControls(touchX, touchY)) {
+ // avoid unintended focus event if we are tapping on our window controls
+ // so that keyboard doesn't possibly show briefly
+ return false;
+ }
+
boolean clickedInside = (touchX >= x) && (touchX <= (x + layoutParams.width)) && (touchY >= y) && (touchY <= (y + layoutParams.height));
switch (event.getAction()) {
@@ -163,15 +191,31 @@ public boolean onInterceptTouchEvent(MotionEvent event) {
return false;
}
+ private boolean didClickInsideWindowControls(float touchX, float touchY) {
+ if (mWindowControls.getVisibility() == View.GONE) {
+ return false;
+ }
+ mWindowControls.getLocationOnScreen(windowControlsLocation);
+ int controlsX = windowControlsLocation[0];
+ int controlsY = windowControlsLocation[1];
+
+ return (touchX >= controlsX && touchX <= controlsX + mWindowControls.getWidth()) &&
+ (touchY >= controlsY && touchY <= controlsY + mWindowControls.getHeight());
+ }
+
void showTouchKeyboard() {
mTerminalView.post(() -> imm.showSoftInput(mTerminalView, InputMethodManager.SHOW_IMPLICIT));
}
+ void hideTouchKeyboard() {
+ mTerminalView.post(() -> imm.hideSoftInputFromWindow(mTerminalView.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY));
+ }
+
void updateLongPressMode(boolean newValue) {
isInLongPressState = newValue;
- setBackgroundResource(newValue ? R.drawable.floating_window_background_resize : R.drawable.floating_window_background);
+ mFloatingBubbleManager.updateLongPressBackgroundResource(isInLongPressState);
setAlpha(newValue ? ALPHA_MOVING : (withFocus ? ALPHA_FOCUS : ALPHA_NOT_FOCUS));
- if (newValue) {
+ if (newValue && !mFloatingBubbleManager.isMinimized()) {
Toast toast = Toast.makeText(getContext(), R.string.after_long_press, Toast.LENGTH_SHORT);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
@@ -207,6 +251,9 @@ public boolean onTouchEvent(MotionEvent event) {
* Visually indicate focus and show the soft input as needed.
*/
void changeFocus(boolean newFocus) {
+ if (newFocus && mFloatingBubbleManager.isMinimized()) {
+ mFloatingBubbleManager.displayAsFloatingWindow();
+ }
if (newFocus == withFocus) {
if (newFocus) showTouchKeyboard();
return;
@@ -219,5 +266,12 @@ void changeFocus(boolean newFocus) {
public void closeFloatingWindow() {
mWindowManager.removeView(this);
+ mFloatingBubbleManager.cleanup();
+ mFloatingBubbleManager = null;
+ }
+
+ private void exit() {
+ Intent intent = new Intent(getContext(), TermuxFloatService.class);
+ getContext().stopService(intent);
}
}
diff --git a/app/src/main/res/drawable/ic_exit_icon.xml b/app/src/main/res/drawable/ic_exit_icon.xml
new file mode 100644
index 0000000..12f9875
--- /dev/null
+++ b/app/src/main/res/drawable/ic_exit_icon.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_minimize_icon.xml b/app/src/main/res/drawable/ic_minimize_icon.xml
new file mode 100644
index 0000000..918d3a3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_minimize_icon.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/round_button.xml b/app/src/main/res/drawable/round_button.xml
new file mode 100644
index 0000000..c5f59c9
--- /dev/null
+++ b/app/src/main/res/drawable/round_button.xml
@@ -0,0 +1,8 @@
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/round_button_with_outline.xml b/app/src/main/res/drawable/round_button_with_outline.xml
new file mode 100644
index 0000000..0a14446
--- /dev/null
+++ b/app/src/main/res/drawable/round_button_with_outline.xml
@@ -0,0 +1,10 @@
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 8acbdd3..65b0b10 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -4,8 +4,38 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/floating_window_background"
+ android:orientation="vertical"
android:padding="1px" >
+
+
+
+
+
+