Long click to move; Boosty page on click before redirect

This commit is contained in:
utkabobr
2025-04-04 19:18:57 +03:00
parent 264e742d3a
commit 5f13961d05
13 changed files with 215 additions and 25 deletions
@@ -8,11 +8,19 @@ import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.text.style.ReplacementSpan;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
@@ -30,6 +38,7 @@ import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
@@ -94,9 +103,11 @@ import ru.ytkab0bp.slicebeam.view.BeamSwitch;
import ru.ytkab0bp.slicebeam.view.BoostySubsView;
import ru.ytkab0bp.slicebeam.view.FadeRecyclerView;
import ru.ytkab0bp.slicebeam.view.MiniColorView;
import ru.ytkab0bp.slicebeam.view.TextColorImageSpan;
public class SetupActivity extends AppCompatActivity {
public final static String EXTRA_ABOUT = "about";
public final static String EXTRA_BOOSTY_ONLY = "boosty_only";
private final static String TAG = "SetupActivity";
@@ -137,6 +148,7 @@ public class SetupActivity extends AppCompatActivity {
private Map<ProfilesRepo, List<Slic3rConfigWrapper>> profilesMap = new HashMap<>();
private boolean isProfilesLoaded;
private boolean about;
private boolean boostyOnly;
private List<ConfigObject> enabledPrinters = new ArrayList<>();
@@ -153,8 +165,9 @@ public class SetupActivity extends AppCompatActivity {
SliceBeam.EVENT_BUS.registerListener(this);
about = getIntent().getBooleanExtra(EXTRA_ABOUT, false);
boostyOnly = getIntent().getBooleanExtra(EXTRA_BOOSTY_ONLY, false);
if (!about) {
if (!about && !boostyOnly) {
new BeamAlertDialogBuilder(this)
.setTitle(R.string.IntroEarlyAccess)
.setMessage(R.string.IntroEarlyAccessMessage)
@@ -166,7 +179,7 @@ public class SetupActivity extends AppCompatActivity {
adapter = new SimpleRecyclerAdapter() {
@Override
public int getItemCount() {
return about ? 1 : limitRepoFragmentCount ? REPOS_INDEX + 1 : limitProfileFragmentCount ? PROFILES_INDEX + 1 : super.getItemCount();
return about || boostyOnly ? 1 : limitRepoFragmentCount ? REPOS_INDEX + 1 : limitProfileFragmentCount ? PROFILES_INDEX + 1 : super.getItemCount();
}
};
setItems();
@@ -197,13 +210,15 @@ public class SetupActivity extends AppCompatActivity {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (position == 0) {
if (position == 0 && !boostyOnly) {
backgroundProgress = positionOffset;
} else {
backgroundProgress = 1f;
}
if (position == BOOSTY_INDEX) {
if (boostyOnly) {
boostyProgress = 1f;
} else if (position == BOOSTY_INDEX) {
boostyProgress = 1f - positionOffset;
} else if (position == BOOSTY_INDEX - 1) {
boostyProgress = positionOffset;
@@ -462,7 +477,9 @@ public class SetupActivity extends AppCompatActivity {
}
private void setItems() {
if (about) {
if (boostyOnly) {
adapter.setItems(Collections.singletonList(new BoostyItem()));
} else if (about) {
adapter.setItems(Collections.singletonList(new AboutItem()));
} else {
List<SimpleRecyclerItem> items = new ArrayList<>(Arrays.asList(
@@ -1008,25 +1025,42 @@ public class SetupActivity extends AppCompatActivity {
}});
TextView subscribeButton = new TextView(ctx);
subscribeButton.setText(R.string.IntroBoostySupport);
SpannableStringBuilder sb = SpannableStringBuilder.valueOf(ctx.getString(R.string.IntroBoostySupport)).append(" ");
Drawable dr = ContextCompat.getDrawable(ctx, R.drawable.external_link_outline_24);
int size = ViewUtils.dp(16);
dr.setBounds(0, 0, size, size);
sb.append("d", new TextColorImageSpan(dr, ViewUtils.dp(2f)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
subscribeButton.setText(sb);
subscribeButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
subscribeButton.setTextColor(ThemesRepo.getColor(R.attr.boostyColorTop));
subscribeButton.setTextColor(Color.WHITE);
subscribeButton.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
subscribeButton.setGravity(Gravity.CENTER);
subscribeButton.setPadding(ViewUtils.dp(12), ViewUtils.dp(8), ViewUtils.dp(12), ViewUtils.dp(8));
subscribeButton.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 16));
subscribeButton.setOnClickListener(v -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://boosty.to/ytkab0bp"))));
ll.addView(subscribeButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
bottomMargin = ViewUtils.dp(12);
ll.addView(subscribeButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)) {{
leftMargin = rightMargin = ViewUtils.dp(16);
bottomMargin = ViewUtils.dp(8);
}});
TextView buttonView = new TextView(ctx);
buttonView.setText(R.string.IntroNext);
if (boostyOnly) {
buttonView.setText(android.R.string.ok);
} else {
buttonView.setText(R.string.IntroNext);
}
buttonView.setTextColor(ThemesRepo.getColor(R.attr.textColorOnAccent));
buttonView.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
buttonView.setGravity(Gravity.CENTER);
buttonView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
buttonView.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), ThemesRepo.getColor(R.attr.boostyColorTop), 16));
buttonView.setOnClickListener(v-> scrollToNext());
buttonView.setOnClickListener(v-> {
if (boostyOnly) {
finish();
return;
}
scrollToNext();
});
ll.addView(buttonView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)) {{
leftMargin = rightMargin = ViewUtils.dp(16);
bottomMargin = ViewUtils.dp(16);
@@ -31,6 +31,7 @@ import ru.ytkab0bp.slicebeam.SliceBeam;
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
import ru.ytkab0bp.slicebeam.components.UnfoldMenu;
import ru.ytkab0bp.slicebeam.events.FlattenModeResetEvent;
import ru.ytkab0bp.slicebeam.events.LongClickTranslationEvent;
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
import ru.ytkab0bp.slicebeam.events.ObjectsListChangedEvent;
import ru.ytkab0bp.slicebeam.events.SelectedObjectChangedEvent;
@@ -139,7 +140,7 @@ public class OrientationMenu extends ListBedMenu {
private Vec3d tempVec = new Vec3d();
private int startedScrollObject;
private void translateVisual(Double x, Double y, Double z) {
public void translateVisual(Double x, Double y, Double z) {
int j = fragment.getGlView().getRenderer().getSelectedObject();
if (j == -1) return;
startedScrollObject = j;
@@ -395,6 +396,24 @@ public class OrientationMenu extends ListBedMenu {
startedScrollObject = -1;
}
@EventHandler(runOnMainThread = true)
public void onLongClickTranslation(LongClickTranslationEvent e) {
if (e.visual) {
int j = fragment.getGlView().getRenderer().getSelectedObject();
if (j == -1) return;
Model model = fragment.getGlView().getRenderer().getModel();
model.getTranslation(j, tempVec);
xTitle.setText(formatTrackTitle(R.string.MenuOrientationPositionXValue, tempVec.x + e.x));
yTitle.setText(formatTrackTitle(R.string.MenuOrientationPositionYValue, tempVec.y + e.y));
xTrack.setCurrentPosition((int) (tempVec.x + e.x));
yTrack.setCurrentPosition((int) (tempVec.y + e.y));
} else {
setSelectionValues();
}
}
@EventHandler(runOnMainThread = true)
public void onSelectedObjectChanged(SelectedObjectChangedEvent e) {
stopScroll();
@@ -0,0 +1,16 @@
package ru.ytkab0bp.slicebeam.events;
import ru.ytkab0bp.eventbus.Event;
@Event
public class LongClickTranslationEvent {
public final double x;
public final double y;
public final boolean visual;
public LongClickTranslationEvent(double x, double y, boolean visual) {
this.x = x;
this.y = y;
this.visual = visual;
}
}
@@ -144,7 +144,7 @@ public class SettingsFragment extends ProfileListFragment {
}),
BeamServerData.isBoostyAvailable() ? new OptionElement(R.drawable.boosty, getContext().getString(R.string.SettingsBoosty)).setColor(R.attr.boostyColorTop, true).setOnClick(() -> {
Activity act = (Activity) getContext();
act.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://boosty.to/ytkab0bp")));
act.startActivity(new Intent(act, SetupActivity.class).putExtra(SetupActivity.EXTRA_BOOSTY_ONLY, true));
}) : null,
new OptionElement(R.drawable.k3d_logo_new_14, getContext().getString(R.string.SettingsK3D)).setColor(R.attr.k3dColor, true).setOnClick(() -> {
Activity act = (Activity) getContext();
@@ -107,6 +107,18 @@ public class MobileNavigationDelegate extends DelegateSlotImpl {
return root = fl;
}
@Override
public boolean onBackPressed() {
if (super.onBackPressed()) {
return true;
}
if (currentSlot != 0) {
switchSlot(0, () -> navigationView.setSelectedItemId(0));
return true;
}
return false;
}
@Override
public FrameLayout getOverlayView() {
return root;
@@ -41,7 +41,7 @@ public class Camera {
this.zoom = zoom;
}
public void move(float x, float y) {
public Vec3d calcScreenMovement(float x, float y) {
x /= zoom;
y /= zoom;
@@ -61,8 +61,10 @@ public class Camera {
screenX.multiply(x);
screenY.multiply(y);
Vec3d move = new Vec3d(screenX).add(screenY);
return new Vec3d(screenX).add(screenY);
}
public void move(float x, float y) {
Vec3d move = calcScreenMovement(x, y);
position.add(move);
origin.add(move);
}
@@ -374,9 +374,8 @@ public class GLRenderer implements GLSurfaceView.Renderer {
return true;
}
public boolean onClick(float x, float y) {
if (model == null || isViewerEnabled) return false;
public int raycastObjectIndex(float x, float y) {
if (model == null) return -1;
double minDistance = Double.MAX_VALUE;
int j = -1;
for (int i = 0, c = model.getObjectsCount(); i < c; i++) {
@@ -394,6 +393,13 @@ public class GLRenderer implements GLSurfaceView.Renderer {
}
}
}
return j;
}
public boolean onClick(float x, float y) {
if (model == null || isViewerEnabled) return false;
int j = raycastObjectIndex(x, y);
if (isInFlattenMode && (j == selectedObject || j == -1)) {
int minPlane = -1;
@@ -45,6 +45,7 @@ public class Bed3D {
private void configure(String path) {
Native.bed_configure(pointer, path);
Native.bed_init_triangles_mesh(pointer, triangles.pointer);
boundingVolume = Native.bed_get_bounding_volume(pointer);
min = max = null;
@@ -78,6 +79,10 @@ public class Bed3D {
return boundingVolume != null;
}
public GLModel.MeshRaycaster getRaycaster() {
return triangles.getRaycaster();
}
public void render(boolean bottom, double[] viewModelMatrix, double[] projectionMatrix, float invZoom) {
assertTrue(viewModelMatrix.length == 16);
assertTrue(projectionMatrix.length == 16);
@@ -46,6 +46,7 @@ class Native {
static native int bed_get_bounding_volume_max_size(long ptr);
static native double[] bed_get_bounding_volume(long ptr);
static native void bed_configure(long ptr, String configPath);
static native void bed_init_triangles_mesh(long ptr, long triangles);
static native boolean bed_arrange(long ptr, long modelPtr);
static native void bed_release(long ptr);
@@ -17,17 +17,23 @@ import android.opengl.GLSurfaceView;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.ViewConfiguration;
import java.nio.IntBuffer;
import java.util.ArrayList;
import ru.ytkab0bp.slicebeam.R;
import ru.ytkab0bp.slicebeam.SliceBeam;
import ru.ytkab0bp.slicebeam.events.LongClickTranslationEvent;
import ru.ytkab0bp.slicebeam.render.GLRenderer;
import ru.ytkab0bp.slicebeam.slic3r.GLModel;
import ru.ytkab0bp.slicebeam.theme.IThemeView;
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
import ru.ytkab0bp.slicebeam.utils.Prefs;
import ru.ytkab0bp.slicebeam.utils.Vec3d;
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
public class GLView extends GLSurfaceView implements IThemeView {
@@ -40,9 +46,26 @@ public class GLView extends GLSurfaceView implements IThemeView {
private boolean fromTwoPointers;
private boolean onePointerGesture;
private boolean twoPointerGesture;
private boolean longClickGesture;
private boolean isScaling;
private Vec3d tempVec = new Vec3d();
private Vec3d longClickOffset = new Vec3d();
private Vec3d longClickTranslation = new Vec3d();
private ArrayList<GLModel.HitResult> longClickHitResults = new ArrayList<>();
private long lastActionTime = System.currentTimeMillis();
private Runnable longClick = () -> {
getRenderer().getBed().getRaycaster().raycast(getRenderer(), longClickHitResults, lastX, lastY);
if (!longClickHitResults.isEmpty()) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
longClickGesture = true;
GLModel.HitResult result = longClickHitResults.get(0);
getRenderer().getModel().getTranslation(getRenderer().getSelectedObject(), tempVec);
longClickOffset.x = result.position.x - tempVec.x;
longClickOffset.y = result.position.y - tempVec.y;
}
};
private Path path = new Path();
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -249,21 +272,43 @@ public class GLView extends GLSurfaceView implements IThemeView {
int action = e.getActionMasked();
if (e.getPointerCount() > 2) {
removeCallbacks(longClick);
longClickGesture = false;
return true;
}
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
if (e.getPointerCount() == 2) {
removeCallbacks(longClick);
longClickGesture = false;
calcStartFocus(e);
fromTwoPointers = true;
} else {
lastX = e.getX();
lastY = e.getY();
int j = renderer.raycastObjectIndex(lastX, lastY);
if (j == renderer.getSelectedObject() && j != -1) {
postDelayed(longClick, 300);
}
}
return true;
}
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_CANCEL) {
removeCallbacks(longClick);
if (longClickGesture) {
queueEvent(()->{
int j = getRenderer().getSelectedObject();
getRenderer().getModel().getTranslation(j, tempVec);
getRenderer().setSelectionTranslation(0, 0, 0);
getRenderer().getModel().translate(j, longClickTranslation.x, longClickTranslation.y, 0);
getRenderer().invalidateGlModel(j);
requestRender();
SliceBeam.EVENT_BUS.fireEvent(new LongClickTranslationEvent(longClickTranslation.x, longClickTranslation.y, false));
});
}
longClickGesture = false;
if (fromTwoPointers) {
if (e.getPointerCount() == 1) {
fromTwoPointers = false;
@@ -286,7 +331,7 @@ public class GLView extends GLSurfaceView implements IThemeView {
onePointerGesture = false;
}
// TODO: Rotate with inertia
// TODO: Rotate with inertia?
return true;
}
if (action == MotionEvent.ACTION_MOVE) {
@@ -347,18 +392,35 @@ public class GLView extends GLSurfaceView implements IThemeView {
if (Math.sqrt(distanceX * distanceX + distanceY * distanceY) >= touchSlop) {
onePointerGesture = true;
startingGesture = true;
removeCallbacks(longClick);
}
}
if (onePointerGesture) {
if (!startingGesture) {
int mode = Prefs.getCameraControlMode();
if (mode == Prefs.CAMERA_CONTROL_MODE_ROTATE_MOVE) {
renderer.getCamera().rotateAround(distanceX / touchSlop * Prefs.getCameraSensitivity(), distanceY / touchSlop * Prefs.getCameraSensitivity());
if (longClickGesture) {
Vec3d move = getRenderer().getCamera().calcScreenMovement(distanceX / touchSlop * 4.5f, distanceY / touchSlop * 4.5f);
getRenderer().getModel().getTranslation(getRenderer().getSelectedObject(), tempVec);
getRenderer().getBed().getRaycaster().raycast(getRenderer(), longClickHitResults, e.getX(), e.getY());
if (!longClickHitResults.isEmpty()) {
GLModel.HitResult result = longClickHitResults.get(0);
longClickTranslation.x = result.position.x - tempVec.x - longClickOffset.x;
longClickTranslation.y = result.position.y - tempVec.y - longClickOffset.y;
getRenderer().setSelectionTranslation(longClickTranslation.x, longClickTranslation.y, 0);
SliceBeam.EVENT_BUS.fireEvent(new LongClickTranslationEvent(longClickTranslation.x, longClickTranslation.y, true));
}
requestRender();
} else {
renderer.getCamera().move(distanceX / touchSlop * Prefs.getCameraSensitivity(), distanceY / touchSlop * Prefs.getCameraSensitivity());
int mode = Prefs.getCameraControlMode();
if (mode == Prefs.CAMERA_CONTROL_MODE_ROTATE_MOVE) {
renderer.getCamera().rotateAround(distanceX / touchSlop * Prefs.getCameraSensitivity(), distanceY / touchSlop * Prefs.getCameraSensitivity());
} else {
renderer.getCamera().move(distanceX / touchSlop * Prefs.getCameraSensitivity(), distanceY / touchSlop * Prefs.getCameraSensitivity());
}
requestRender();
}
requestRender();
}
lastX = e.getX();
@@ -37,6 +37,10 @@ public class SnackbarsLayout extends FrameLayout {
}
public void show(Snackbar snackbar) {
if (snackbar.tag != null) {
dismiss(snackbar.tag);
}
SnackbarView v = new SnackbarView(getContext()).bind(snackbar);
addView(v);
applyTransforms();
@@ -1401,6 +1401,19 @@ extern "C" {
bed_util_init_contourlines(ref->contour, ref->contourlines);
}
JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_bed_1init_1triangles_1mesh(JNIEnv* env, jclass, jlong ptr, jlong triangles_ptr) {
auto ref = reinterpret_cast<BedRef*>(ptr);
auto tRef = reinterpret_cast<GLModelRef*>(triangles_ptr);
auto contour = ref->contour;
BoundingBox bb = get_extents(contour);
Point center = bb.center();
float scaleFactor = 4;
contour.scale(scaleFactor);
contour.translate(-center.x() * scaleFactor * 0.5f, -center.y() * scaleFactor * 0.5f);
bed_util_init_triangles_its(contour, &tRef->mesh.its);
}
JNIEXPORT jboolean JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_bed_1arrange(JNIEnv* env, jclass, jlong ptr, jlong model) {
BedRef* ref = (BedRef*) (intptr_t) ptr;
ModelRef* mRef = (ModelRef*) (intptr_t) model;
+16
View File
@@ -56,6 +56,22 @@ void bed_util_init_gridlines(ExPolygon& contour, GLModel* glGridlines) {
glGridlines->init_from(std::move(init_data));
}
void bed_util_init_triangles_its(ExPolygon& contour, indexed_triangle_set* its) {
if (contour.empty())
return;
auto triangles = triangulate_expolygon_3d(contour, 0);
its->vertices.reserve(triangles.size());
for (size_t i = 0; i < triangles.size(); i += 3) {
its->vertices.emplace_back(triangles[i].cast<float>());
its->vertices.emplace_back(triangles[i + 1].cast<float>());
its->vertices.emplace_back(triangles[i + 2].cast<float>());
its->indices.emplace_back(i, i + 1, i + 2);
}
}
void bed_util_init_triangles(ExPolygon& contour, GLModel* glTriangles) {
if (glTriangles->is_initialized())
return;