From 1976ac48996abaf45b149d5462b6b013dbffead8 Mon Sep 17 00:00:00 2001 From: utkabobr Date: Wed, 2 Apr 2025 01:38:45 +0300 Subject: [PATCH] New snackbars --- .../ru/ytkab0bp/slicebeam/MainActivity.java | 81 +++-- .../events/NeedDismissSnackbarEvent.java | 12 + .../slicebeam/events/NeedSnackbarEvent.java | 18 + .../slicebeam/fragment/BedFragment.java | 24 +- .../ru/ytkab0bp/slicebeam/slic3r/Model.java | 12 + .../ru/ytkab0bp/slicebeam/slic3r/Native.java | 3 + .../ytkab0bp/slicebeam/theme/BeamTheme.java | 8 + .../ru/ytkab0bp/slicebeam/utils/IOUtils.java | 5 + .../slicebeam/view/SnackbarsLayout.java | 307 ++++++++++++++++++ app/src/main/jni/slicebeam/beam_native.cpp | 19 ++ app/src/main/res/drawable/done_outline_28.xml | 12 + .../main/res/drawable/error_outline_28.xml | 12 + .../drawable/warning_triangle_outline_28.xml | 15 + app/src/main/res/values-ru/strings.xml | 4 +- app/src/main/res/values/attrs.xml | 5 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/values/themes.xml | 1 - 17 files changed, 507 insertions(+), 33 deletions(-) create mode 100644 app/src/main/java/ru/ytkab0bp/slicebeam/events/NeedDismissSnackbarEvent.java create mode 100644 app/src/main/java/ru/ytkab0bp/slicebeam/view/SnackbarsLayout.java create mode 100644 app/src/main/res/drawable/done_outline_28.xml create mode 100644 app/src/main/res/drawable/error_outline_28.xml create mode 100644 app/src/main/res/drawable/warning_triangle_outline_28.xml diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/MainActivity.java b/app/src/main/java/ru/ytkab0bp/slicebeam/MainActivity.java index ad86bd3..0ee0f3a 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/MainActivity.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/MainActivity.java @@ -2,14 +2,20 @@ package ru.ytkab0bp.slicebeam; import android.app.Activity; import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Intent; import android.content.res.Configuration; import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Environment; +import android.os.Process; import android.provider.MediaStore; +import android.util.Base64; import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; @@ -28,6 +34,7 @@ import org.json.JSONArray; import org.json.JSONObject; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -40,23 +47,27 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Objects; +import java.util.UUID; import java.util.zip.ZipFile; import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder; import ru.ytkab0bp.slicebeam.components.ChangeLogBottomSheet; import ru.ytkab0bp.slicebeam.components.UnfoldMenu; import ru.ytkab0bp.slicebeam.config.ConfigObject; +import ru.ytkab0bp.slicebeam.events.NeedDismissSnackbarEvent; import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent; import ru.ytkab0bp.slicebeam.events.ObjectsListChangedEvent; import ru.ytkab0bp.slicebeam.fragment.BedFragment; import ru.ytkab0bp.slicebeam.navigation.MobileNavigationDelegate; import ru.ytkab0bp.slicebeam.navigation.NavigationDelegate; +import ru.ytkab0bp.slicebeam.slic3r.Model; import ru.ytkab0bp.slicebeam.slic3r.Slic3rConfigWrapper; import ru.ytkab0bp.slicebeam.slic3r.Slic3rRuntimeError; import ru.ytkab0bp.slicebeam.theme.ThemesRepo; import ru.ytkab0bp.slicebeam.utils.IOUtils; import ru.ytkab0bp.slicebeam.utils.Prefs; import ru.ytkab0bp.slicebeam.utils.ViewUtils; +import ru.ytkab0bp.slicebeam.view.SnackbarsLayout; public class MainActivity extends AppCompatActivity { public final static int REQUEST_CODE_OPEN_FILE = 1, REQUEST_CODE_EXPORT_GCODE = 2, @@ -496,6 +507,51 @@ public class MainActivity extends AppCompatActivity { }).start(); } + private void loadFile(File f, boolean autoorient) { + String tag = UUID.randomUUID().toString(); + SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.LOADING, R.string.MenuFileOpenFileLoading).tag(tag)); + IOUtils.IO_POOL.submit(() -> { + Process.setThreadPriority(-20); + if (delegate.getCurrentFragment() instanceof BedFragment) { + BedFragment fragment = (BedFragment) delegate.getCurrentFragment(); + try { + boolean gcode = f.getName().endsWith(".gcode"); + if (gcode) { + fragment.loadGCode(f); + } else { + fragment.loadModel(f); + } + fragment.getGlView().queueEvent(() -> { + if (!gcode) { + SliceBeam.EVENT_BUS.fireEvent(new ObjectsListChangedEvent()); + } + Model model = fragment.getGlView().getRenderer().getModel(); + int i = model.getObjectsCount() - 1; + if (autoorient) { + model.autoOrient(i); + fragment.getGlView().getRenderer().invalidateGlModel(i); + fragment.getGlView().requestRender(); + } + SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(tag)); + SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.MenuFileOpenFileLoaded)); + if (model.isBigObject(i)) { + SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.WARNING, R.string.MenuFileOpenFileBigObject)); + } + }); + } catch (Slic3rRuntimeError e) { + Log.e("MainActivity", "Failed to load model", e); + f.delete(); + + ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(this) + .setTitle(R.string.MenuFileOpenFileFailed) + .setMessage(e.toString()) + .setPositiveButton(android.R.string.ok, null) + .show()); + } + } + }); + } + private void loadFile(Uri uri) { if (uri == null) return; @@ -534,30 +590,7 @@ public class MainActivity extends AppCompatActivity { } fos.close(); in.close(); - - ViewUtils.postOnMainThread(() -> { - if (delegate.getCurrentFragment() instanceof BedFragment) { - BedFragment fragment = (BedFragment) delegate.getCurrentFragment(); - try { - if (f.getName().endsWith(".gcode")) { - fragment.loadGCode(f); - } else { - fragment.loadModel(f); - SliceBeam.EVENT_BUS.fireEvent(new ObjectsListChangedEvent()); - } - SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.MenuFileOpenFileLoaded)); - } catch (Slic3rRuntimeError e) { - Log.e("MainActivity", "Failed to load model", e); - f.delete(); - - ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(this) - .setTitle(R.string.MenuFileOpenFileFailed) - .setMessage(e.toString()) - .setPositiveButton(android.R.string.ok, null) - .show()); - } - } - }); + loadFile(f, false); } catch (Exception e) { Log.e("MainActivity", "Failed to write cache file", e); diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/events/NeedDismissSnackbarEvent.java b/app/src/main/java/ru/ytkab0bp/slicebeam/events/NeedDismissSnackbarEvent.java new file mode 100644 index 0000000..eb04dbb --- /dev/null +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/events/NeedDismissSnackbarEvent.java @@ -0,0 +1,12 @@ +package ru.ytkab0bp.slicebeam.events; + +import ru.ytkab0bp.eventbus.Event; + +@Event +public class NeedDismissSnackbarEvent { + public final String tag; + + public NeedDismissSnackbarEvent(String tag) { + this.tag = tag; + } +} diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/events/NeedSnackbarEvent.java b/app/src/main/java/ru/ytkab0bp/slicebeam/events/NeedSnackbarEvent.java index 783a353..b10115b 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/events/NeedSnackbarEvent.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/events/NeedSnackbarEvent.java @@ -2,10 +2,18 @@ package ru.ytkab0bp.slicebeam.events; import ru.ytkab0bp.eventbus.Event; import ru.ytkab0bp.slicebeam.SliceBeam; +import ru.ytkab0bp.slicebeam.view.SnackbarsLayout; @Event public class NeedSnackbarEvent { public final CharSequence title; + public SnackbarsLayout.Type type = SnackbarsLayout.Type.DONE; + public String tag; + + public NeedSnackbarEvent(SnackbarsLayout.Type type, CharSequence title) { + this.type = type; + this.title = title; + } public NeedSnackbarEvent(CharSequence title) { this.title = title; @@ -14,4 +22,14 @@ public class NeedSnackbarEvent { public NeedSnackbarEvent(int title, Object... args) { this.title = SliceBeam.INSTANCE.getString(title, args); } + + public NeedSnackbarEvent(SnackbarsLayout.Type type, int title, Object... args) { + this.type = type; + this.title = SliceBeam.INSTANCE.getString(title, args); + } + + public NeedSnackbarEvent tag(String tag) { + this.tag = tag; + return this; + } } diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/fragment/BedFragment.java b/app/src/main/java/ru/ytkab0bp/slicebeam/fragment/BedFragment.java index 74a1071..c5c5b76 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/fragment/BedFragment.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/fragment/BedFragment.java @@ -14,13 +14,11 @@ import android.webkit.WebView; import android.widget.FrameLayout; import android.widget.LinearLayout; -import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.dynamicanimation.animation.FloatValueHolder; import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; import com.google.android.material.navigation.NavigationBarView; -import com.google.android.material.snackbar.Snackbar; import java.io.File; @@ -38,6 +36,7 @@ import ru.ytkab0bp.slicebeam.components.bed_menu.SliceMenu; import ru.ytkab0bp.slicebeam.components.bed_menu.TransformMenu; import ru.ytkab0bp.slicebeam.config.ConfigObject; import ru.ytkab0bp.slicebeam.events.FlattenModeResetEvent; +import ru.ytkab0bp.slicebeam.events.NeedDismissSnackbarEvent; import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent; import ru.ytkab0bp.slicebeam.events.SlicingProgressEvent; import ru.ytkab0bp.slicebeam.navigation.Fragment; @@ -51,6 +50,7 @@ import ru.ytkab0bp.slicebeam.utils.ViewUtils; import ru.ytkab0bp.slicebeam.view.BedSwipeDownLayout; import ru.ytkab0bp.slicebeam.view.DividerView; import ru.ytkab0bp.slicebeam.view.GLView; +import ru.ytkab0bp.slicebeam.view.SnackbarsLayout; import ru.ytkab0bp.slicebeam.view.ThemeBottomNavigationView; import ru.ytkab0bp.slicebeam.view.ThemeRailNavigationView; @@ -59,7 +59,7 @@ public class BedFragment extends Fragment { private final static int MENU_SIZE_DP = 80; private FrameLayout overlayLayout; - private CoordinatorLayout snackbarsLayout; + private SnackbarsLayout snackbarsLayout; private GLView glView; private NavigationBarView navigationView; @@ -126,7 +126,16 @@ public class BedFragment extends Fragment { @EventHandler(runOnMainThread = true) public void onNeedSnackbar(NeedSnackbarEvent e) { - Snackbar.make(snackbarsLayout, e.title, Snackbar.LENGTH_SHORT).show(); + SnackbarsLayout.Snackbar s = new SnackbarsLayout.Snackbar(e.type, e.title); + if (e.tag != null) { + s.tag(e.tag); + } + snackbarsLayout.show(s); + } + + @EventHandler(runOnMainThread = true) + public void onDismissSnackbar(NeedDismissSnackbarEvent e) { + snackbarsLayout.dismiss(e.tag); } public void showUnfoldMenu(UnfoldMenu menu, View from) { @@ -380,7 +389,7 @@ public class BedFragment extends Fragment { } else { overlayLayout.addView(contentView = ll); } - overlayLayout.addView(snackbarsLayout = new CoordinatorLayout(ctx), new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) {{ + overlayLayout.addView(snackbarsLayout = new SnackbarsLayout(ctx), new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) {{ if (portrait) { bottomMargin = ViewUtils.dp(80 * 2); } else { @@ -390,7 +399,7 @@ public class BedFragment extends Fragment { return overlayLayout; } - public CoordinatorLayout getSnackbarsLayout() { + public SnackbarsLayout getSnackbarsLayout() { return snackbarsLayout; } @@ -498,7 +507,6 @@ public class BedFragment extends Fragment { } }); } else { - glView.getRenderer().setModel(model = m); glView.queueEvent(new Runnable() { @Override public void run() { @@ -507,6 +515,8 @@ public class BedFragment extends Fragment { ViewUtils.postOnMainThread(()-> glView.queueEvent(this)); return; } + glView.getRenderer().setModel(model = m); + Vec3d center = bed.getVolumeMin().center(bed.getVolumeMax()); Vec3d objMin = new Vec3d(), objMax = new Vec3d(); Vec3d objTranslate = new Vec3d(); diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Model.java b/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Model.java index 229ced6..51f9055 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Model.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Model.java @@ -147,10 +147,22 @@ public class Model { return list; } + public int getExtruder(int i) { + return Native.model_get_extruder(pointer, i); + } + + public void setExtruder(int i, int extruder) { + Native.model_set_extruder(pointer, i, extruder); + } + public void autoOrient(int i) { Native.model_auto_orient(pointer, i); } + public boolean isBigObject(int i) { + return Native.model_is_big_object(pointer, i); + } + public GCodeProcessorResult slice(String configPath, String gcodePath, SliceListener listener) throws Slic3rRuntimeError { return new GCodeProcessorResult(Native.model_slice(pointer, configPath, gcodePath, listener)); } diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Native.java b/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Native.java index 9a1751b..693cf0c 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Native.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Native.java @@ -72,6 +72,9 @@ class Native { static native void model_flatten_rotate(long ptr, int i, long surfacePtr); static native long[] model_create_flatten_planes(long ptr, int i); static native void model_auto_orient(long ptr, int i); + static native boolean model_is_big_object(long ptr, int i); + static native int model_get_extruder(long ptr, int i); + static native void model_set_extruder(long ptr, int i, int extruder); static native long model_slice(long ptr, String configPath, String path, SliceListener listener) throws Slic3rRuntimeError; static native void model_release(long ptr); diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/theme/BeamTheme.java b/app/src/main/java/ru/ytkab0bp/slicebeam/theme/BeamTheme.java index 72925b7..19d01e8 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/theme/BeamTheme.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/theme/BeamTheme.java @@ -47,6 +47,12 @@ public class BeamTheme { colors.put(R.attr.yTrackColor, 0xff00bf00); colors.put(R.attr.zTrackColor, 0xff0000bf); + colors.put(R.attr.snackbarBase, 0xFFEEEEEE); + colors.put(R.attr.snackbarDone, 0xFF56AB2F); + colors.put(R.attr.snackbarWarning, 0xFFAE660C); + colors.put(R.attr.snackbarInfo, 0xFF009DC6); + colors.put(R.attr.snackbarError, 0xFFDC100E); + colors.put(android.R.attr.textColorPrimary, 0xff000000); colors.put(android.R.attr.textColorSecondary, 0x99000000); colors.put(android.R.attr.windowBackground, 0xffffffff); @@ -73,6 +79,8 @@ public class BeamTheme { colors.put(R.attr.yTrackColor, 0xff00ee00); colors.put(R.attr.zTrackColor, 0xff0000ee); + colors.put(R.attr.snackbarBase, 0xFF212121); + colors.put(android.R.attr.textColorPrimary, 0xffffffff); colors.put(android.R.attr.textColorSecondary, 0x99ffffff); colors.put(android.R.attr.windowBackground, 0xff121212); diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/utils/IOUtils.java b/app/src/main/java/ru/ytkab0bp/slicebeam/utils/IOUtils.java index c65588f..efe9cda 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/utils/IOUtils.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/utils/IOUtils.java @@ -15,10 +15,15 @@ import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import ru.ytkab0bp.slicebeam.config.ConfigObject; public class IOUtils { + public static ExecutorService IO_POOL = Executors.newCachedThreadPool(); + public static String readString(InputStream in) throws IOException { return readString(in, false); } diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/view/SnackbarsLayout.java b/app/src/main/java/ru/ytkab0bp/slicebeam/view/SnackbarsLayout.java new file mode 100644 index 0000000..a9b01be --- /dev/null +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/view/SnackbarsLayout.java @@ -0,0 +1,307 @@ +package ru.ytkab0bp.slicebeam.view; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Outline; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewOutlineProvider; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.graphics.ColorUtils; +import androidx.dynamicanimation.animation.FloatValueHolder; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import java.util.Objects; + +import ru.ytkab0bp.slicebeam.R; +import ru.ytkab0bp.slicebeam.theme.ThemesRepo; +import ru.ytkab0bp.slicebeam.utils.ViewUtils; + +public class SnackbarsLayout extends FrameLayout { + public SnackbarsLayout(@NonNull Context context) { + super(context); + } + + public void show(Snackbar snackbar) { + SnackbarView v = new SnackbarView(getContext()).bind(snackbar); + addView(v); + applyTransforms(); + new SpringAnimation(new FloatValueHolder(0)) + .setMinimumVisibleChange(1 / 500f) + .setSpring(new SpringForce(1f) + .setStiffness(1000f) + .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)) + .addUpdateListener((animation, value, velocity) -> { + v.progress = value; + applyTransforms(); + }) + .start(); + + if (snackbar.lifetime > 0) { + ViewUtils.postOnMainThread(v::dismiss, snackbar.lifetime); + } + } + + public void dismiss(String tag) { + for (int i = 0, s = getChildCount(); i < s; i++) { + SnackbarView snackbar = (SnackbarView) getChildAt(i); + if (Objects.equals(snackbar.snackbar.tag, tag)) { + snackbar.dismiss(); + } + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + applyTransforms(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + applyTransforms(); + } + + private void applyTransforms() { + float y = getHeight() - ViewUtils.dp(8); + + for (int i = getChildCount() - 1; i >= 0; i--) { + SnackbarView snackbar = (SnackbarView) getChildAt(i); + if (snackbar.getTag() == null) { + snackbar.setAlpha(snackbar.progress); + } + + float yOff = snackbar.getAlpha() * snackbar.progress * (snackbar.getHeight() + ViewUtils.dp(8)); + y -= yOff; + snackbar.setTranslationY(y); + } + } + + private class SnackbarView extends LinearLayout { + private final static int MARGIN_DP = 8; + + private ProgressBar progressBar; + private ImageView icon; + private TextView title; + private Snackbar snackbar; + + private float progress; + + private GestureDetector gestureDetector; + + SnackbarView(Context context) { + super(context); + + setElevation(ViewUtils.dp(4)); + setClipToOutline(true); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(0, 0, getWidth(), getHeight(), ViewUtils.dp(16)); + } + }); + setAlpha(0f); + setPadding(ViewUtils.dp(10), ViewUtils.dp(10), ViewUtils.dp(10), ViewUtils.dp(10)); + setMinimumHeight(ViewUtils.dp(48)); + + setOrientation(HORIZONTAL); + setGravity(Gravity.CENTER_VERTICAL); + + FrameLayout fl = new FrameLayout(context); + icon = new ImageView(context); + fl.addView(icon, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + progressBar = new ProgressBar(context); + progressBar.setVisibility(GONE); + fl.addView(progressBar, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + addView(fl, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)) {{ + setMarginStart(ViewUtils.dp(4)); + setMarginEnd(ViewUtils.dp(14)); + }}); + title = new TextView(context); + title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM)); + title.setMaxLines(2); + title.setEllipsize(TextUtils.TruncateAt.END); + addView(title); + + setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{ + leftMargin = topMargin = rightMargin = bottomMargin = ViewUtils.dp(MARGIN_DP); + }}); + + gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDown(@NonNull MotionEvent e) { + return true; + } + + @Override + public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) { + getParent().requestDisallowInterceptTouchEvent(true); + + float off = e2.getX() - e1.getX(); + setTranslationX(off); + return true; + } + + @Override + public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) { + if (snackbar.type == Type.LOADING) { + return false; + } + + if (Math.abs(velocityX) >= 1500) { + if (velocityX > 0) { + animateTo(getWidth() + ViewUtils.dp(MARGIN_DP), true); + } else { + animateTo(-getWidth() - ViewUtils.dp(MARGIN_DP), true); + } + return true; + } + + return false; + } + }); + } + + private void dismiss() { + setTag(1); + + new SpringAnimation(new FloatValueHolder(0)) + .setMinimumVisibleChange(1 / 500f) + .setSpring(new SpringForce(1f) + .setStiffness(1000f) + .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)) + .addUpdateListener((animation, value, velocity) -> { + setAlpha(1f - value); + applyTransforms(); + }) + .addEndListener((animation, canceled, value, velocity) -> { + if (getParent() == null) return; + ((ViewGroup) getParent()).removeView(this); + }) + .start(); + } + + private void animateTo(float x, boolean remove) { + if (remove) { + setTag(1); + } + float start = getTranslationX(); + new SpringAnimation(new FloatValueHolder(0)) + .setMinimumVisibleChange(1 / 500f) + .setSpring(new SpringForce(1f) + .setStiffness(1000f) + .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)) + .addUpdateListener((animation, value, velocity) -> { + setTranslationX(ViewUtils.lerp(start, x, value)); + if (remove) { + progress = 1f - value; + applyTransforms(); + } + }) + .addEndListener((animation, canceled, value, velocity) -> { + if (remove) { + ((ViewGroup) getParent()).removeView(SnackbarView.this); + } + }) + .start(); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + if ((event.getActionMasked() == MotionEvent.ACTION_UP || event.getActionMasked() == MotionEvent.ACTION_CANCEL) && getTag() == null) { + animateTo(0, false); + } + + MotionEvent ev = MotionEvent.obtain(event); + ev.offsetLocation(getTranslationX(), 0); + boolean ret = gestureDetector.onTouchEvent(ev); + ev.recycle(); + return ret; + } + + SnackbarView bind(Snackbar snackbar) { + this.snackbar = snackbar; + + progressBar.setVisibility(snackbar.type == Type.LOADING ? VISIBLE : GONE); + icon.setVisibility(snackbar.type == Type.LOADING ? GONE : VISIBLE); + + title.setText(snackbar.title); + switch (snackbar.type) { + case DONE: + icon.setImageResource(R.drawable.done_outline_28); + icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(R.attr.snackbarDone))); + title.setTextColor(ThemesRepo.getColor(R.attr.snackbarDone)); + setBackgroundColor(ColorUtils.blendARGB(ThemesRepo.getColor(R.attr.snackbarBase), ThemesRepo.getColor(R.attr.snackbarDone), 0.15f)); + break; + case WARNING: + icon.setImageResource(R.drawable.warning_triangle_outline_28); + icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(R.attr.snackbarWarning))); + title.setTextColor(ThemesRepo.getColor(R.attr.snackbarWarning)); + setBackgroundColor(ColorUtils.blendARGB(ThemesRepo.getColor(R.attr.snackbarBase), ThemesRepo.getColor(R.attr.snackbarWarning), 0.15f)); + break; + case LOADING: + progressBar.setIndeterminateTintList(ColorStateList.valueOf(ThemesRepo.getColor(R.attr.snackbarInfo))); + title.setTextColor(ThemesRepo.getColor(R.attr.snackbarInfo)); + setBackgroundColor(ColorUtils.blendARGB(ThemesRepo.getColor(R.attr.snackbarBase), ThemesRepo.getColor(R.attr.snackbarInfo), 0.15f)); + break; + case INFO: + icon.setImageResource(R.drawable.info_outline_28); + icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(R.attr.snackbarInfo))); + title.setTextColor(ThemesRepo.getColor(R.attr.snackbarInfo)); + setBackgroundColor(ColorUtils.blendARGB(ThemesRepo.getColor(R.attr.snackbarBase), ThemesRepo.getColor(R.attr.snackbarInfo), 0.15f)); + break; + case ERROR: + icon.setImageResource(R.drawable.error_outline_28); + icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(R.attr.snackbarError))); + title.setTextColor(ThemesRepo.getColor(R.attr.snackbarError)); + setBackgroundColor(ColorUtils.blendARGB(ThemesRepo.getColor(R.attr.snackbarBase), ThemesRepo.getColor(R.attr.snackbarError), 0.15f)); + break; + } + return this; + } + } + + public static class Snackbar { + public CharSequence title; + public Type type; + public int lifetime = 2500; + public String tag; + + public Snackbar(Type type, CharSequence title) { + this.type = type; + this.title = title; + + if (type == Type.WARNING || type == Type.ERROR) { + lifetime = 5000; + } + } + + public Snackbar tag(String tag) { + this.lifetime = 0; + this.tag = tag; + return this; + } + } + + public enum Type { + DONE, WARNING, INFO, ERROR, + LOADING // Must use tag + } +} diff --git a/app/src/main/jni/slicebeam/beam_native.cpp b/app/src/main/jni/slicebeam/beam_native.cpp index ab99764..a9699e5 100644 --- a/app/src/main/jni/slicebeam/beam_native.cpp +++ b/app/src/main/jni/slicebeam/beam_native.cpp @@ -817,6 +817,24 @@ extern "C" { orientation::orient(obj); } + JNIEXPORT jboolean JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_model_1is_1big_1object(JNIEnv* env, jclass, jlong ptr, jint i) { + ModelRef* model = (ModelRef*) (intptr_t) ptr; + ModelObject* obj = model->model.objects[i]; + return obj->volumes.size() == 1 && obj->volumes.front()->mesh().its.indices.size() >= 100000; + } + + JNIEXPORT jint JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_model_1get_1extruder(JNIEnv* env, jclass, jlong ptr, jint i) { + ModelRef* model = (ModelRef*) (intptr_t) ptr; + ModelObject* obj = model->model.objects[i]; + return obj->config.has("extruder") ? obj->config.opt_int("extruder") : -1; + } + + JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_model_1set_1extruder(JNIEnv* env, jclass, jlong ptr, jint i, jint extruder) { + ModelRef* model = (ModelRef*) (intptr_t) ptr; + ModelObject* obj = model->model.objects[i]; + obj->config.set("extruder", extruder); + } + JNIEXPORT jlong JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_model_1slice(JNIEnv* env, jclass, jlong ptr, jstring configPath, jstring path, jobject listener) { try { ModelRef* model = (ModelRef*) (intptr_t) ptr; @@ -1453,6 +1471,7 @@ extern "C" { ref->data = libvgcode_convert_input_data(resultRef->result, resultRef->result.extruder_colors, resultRef->result.extruder_colors, ref->viewer); ref->viewer.load(std::move(ref->data)); + ref->viewer.set_time_mode(libvgcode::ETimeMode::Normal); } JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_vgcode_1reset(JNIEnv* env, jclass, jlong ptr) { diff --git a/app/src/main/res/drawable/done_outline_28.xml b/app/src/main/res/drawable/done_outline_28.xml new file mode 100644 index 0000000..fc9471c --- /dev/null +++ b/app/src/main/res/drawable/done_outline_28.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/error_outline_28.xml b/app/src/main/res/drawable/error_outline_28.xml new file mode 100644 index 0000000..7db20a2 --- /dev/null +++ b/app/src/main/res/drawable/error_outline_28.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/warning_triangle_outline_28.xml b/app/src/main/res/drawable/warning_triangle_outline_28.xml new file mode 100644 index 0000000..2f3aac1 --- /dev/null +++ b/app/src/main/res/drawable/warning_triangle_outline_28.xml @@ -0,0 +1,15 @@ + + + + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 0225efc..297d341 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -10,8 +10,11 @@ Настройки приложения Файл Открыть модель + Файл загружен. Не удалось открыть модель Не удалось определить имя файла. + Файл содержит более 100к треугольников. Нарезка может быть очень медленной. + Загрузка файла… Убрать модель Калибров. K3D Linear Advance @@ -28,7 +31,6 @@ Цилиндр Пирамида Сфера - Файл загружен. Импорт. профилей Не удалось импортировать Не файл .ini diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 9aef6b9..e6a533a 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -34,4 +34,9 @@ + + + + + \ 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 6e101a5..ce2d443 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,6 +14,8 @@ File loaded. Failed to open model Failed to resolve file name. + File has more than 100k triangles. Processing could be really slow. + Loading file… Remove model Calibrat. K3D Linear Advance diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 9a923e5..012179d 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,5 +1,4 @@ -