diff --git a/app_pojavlauncher/build.gradle b/app_pojavlauncher/build.gradle index 51fb8d6fee..1c491df1ca 100644 --- a/app_pojavlauncher/build.gradle +++ b/app_pojavlauncher/build.gradle @@ -118,13 +118,13 @@ dependencies { // implementation 'com.wu-man:android-bsf-api:3.1.3' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.preference:preference:1.1.1' - implementation 'androidx.appcompat:appcompat:1.3.0' + implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.legacy:legacy-preference-v14:1.0.0' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.annotation:annotation:1.2.0' implementation 'androidx.browser:browser:1.3.0' - implementation "androidx.constraintlayout:constraintlayout:2.0.4" + implementation "androidx.constraintlayout:constraintlayout:2.1.1" implementation 'com.github.martin-stone:hsv-alpha-color-picker-android:3.0.1' implementation 'com.github.duanhong169:checkerboarddrawable:1.0.2' diff --git a/app_pojavlauncher/src/main/assets/components/lwjgl3/lwjgl-glfw-classes.jar b/app_pojavlauncher/src/main/assets/components/lwjgl3/lwjgl-glfw-classes.jar index 3ae78b0a78..3c4a31e2b5 100644 Binary files a/app_pojavlauncher/src/main/assets/components/lwjgl3/lwjgl-glfw-classes.jar and b/app_pojavlauncher/src/main/assets/components/lwjgl3/lwjgl-glfw-classes.jar differ diff --git a/app_pojavlauncher/src/main/assets/components/lwjgl3/version b/app_pojavlauncher/src/main/assets/components/lwjgl3/version index ac2bf5d410..e1e41e5ea2 100644 --- a/app_pojavlauncher/src/main/assets/components/lwjgl3/version +++ b/app_pojavlauncher/src/main/assets/components/lwjgl3/version @@ -1 +1 @@ -20210827 +20211023z diff --git a/app_pojavlauncher/src/main/java/android/support/design/widget/VerticalTabLayout.java b/app_pojavlauncher/src/main/java/android/support/design/widget/VerticalTabLayout.java deleted file mode 100644 index 24cbe89c99..0000000000 --- a/app_pojavlauncher/src/main/java/android/support/design/widget/VerticalTabLayout.java +++ /dev/null @@ -1,2329 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.design.widget; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; -import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_DRAGGING; -import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_IDLE; -import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_SETTLING; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.database.DataSetObserver; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.os.Build; -import androidx.annotation.ColorInt; -import androidx.annotation.DrawableRes; -import androidx.annotation.IntDef; -import androidx.annotation.LayoutRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; -import androidx.annotation.StringRes; -import com.google.android.material.R; - -import androidx.core.util.Pools; -import androidx.core.view.GravityCompat; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; -import androidx.viewpager.widget.PagerAdapter; -import androidx.core.view.PointerIconCompat; -import androidx.core.view.ViewCompat; -import androidx.viewpager.widget.ViewPager; -import androidx.core.widget.TextViewCompat; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.appcompat.widget.TooltipCompat; -import android.text.Layout; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.SoundEffectConstants; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityNodeInfo; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.TextView; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.*; -import androidx.core.app.*; - -import com.google.android.material.animation.AnimationUtils; -import com.google.android.material.tabs.TabItem; -import com.google.android.material.tabs.TabLayout; - -/** - * VerticalTabLayout provides a vertical layout to display tabs. - * - *

Population of the tabs to display is - * done through {@link Tab} instances. You create tabs via {@link #newTab()}. From there you can - * change the tab's label or icon via {@link Tab#setText(int)} and {@link Tab#setIcon(int)} - * respectively. To display the tab, you need to add it to the layout via one of the - * {@link #addTab(Tab)} methods. For example: - *

- * VerticalTabLayout tabLayout = ...;
- * tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));
- * tabLayout.addTab(tabLayout.newTab().setText("Tab 2"));
- * tabLayout.addTab(tabLayout.newTab().setText("Tab 3"));
- * 
- * You should set a listener via {@link #setOnTabSelectedListener(OnTabSelectedListener)} to be - * notified when any tab's selection state has been changed. - * - *

You can also add items to VerticalTabLayout in your layout through the use of {@link TabItem}. - * An example usage is like so:

- * - *
- * <android.support.design.widget.TabLayout
- *         android:layout_height="wrap_content"
- *         android:layout_width="match_parent">
- *
- *     <android.support.design.widget.TabItem
- *             android:text="@string/tab_text"/>
- *
- *     <android.support.design.widget.TabItem
- *             android:icon="@drawable/ic_android"/>
- *
- * </android.support.design.widget.TabLayout>
- * 
- * - *

ViewPager integration

- *

- * If you're using a {@link ViewPager} together - * with this layout, you can call {@link #setupWithViewPager(ViewPager)} to link the two together. - * This layout will be automatically populated from the {@link PagerAdapter}'s page titles.

- * - *

- * This view also supports being used as part of a ViewPager's decor, and can be added - * directly to the ViewPager in a layout resource file like so:

- * - *
- * <android.support.v4.view.ViewPager
- *     android:layout_width="match_parent"
- *     android:layout_height="match_parent">
- *
- *     <android.support.design.widget.TabLayout
- *         android:layout_width="match_parent"
- *         android:layout_height="wrap_content"
- *         android:layout_gravity="top" />
- *
- * </android.support.v4.view.ViewPager>
- * 
- * - * @see Tabs - * - * @attr ref android.support.design.R.styleable#TabLayout_tabPadding - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingStart - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingTop - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingEnd - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingBottom - * @attr ref android.support.design.R.styleable#TabLayout_tabContentStart - * @attr ref android.support.design.R.styleable#TabLayout_tabBackground - * @attr ref android.support.design.R.styleable#TabLayout_tabMinWidth - * @attr ref android.support.design.R.styleable#TabLayout_tabMaxWidth - * @attr ref android.support.design.R.styleable#TabLayout_tabTextAppearance - */ -@ViewPager.DecorView -public class VerticalTabLayout extends LinearLayout { - - private static final int DEFAULT_HEIGHT_WITH_TEXT_ICON = 72; // dps - static final int DEFAULT_GAP_TEXT_ICON = 8; // dps - private static final int INVALID_WIDTH = -1; - private static final int DEFAULT_HEIGHT = 48; // dps - private static final int TAB_MIN_WIDTH_MARGIN = 56; //dps - static final int FIXED_WRAP_GUTTER_MIN = 16; //dps - static final int MOTION_NON_ADJACENT_OFFSET = 24; - - private static final int ANIMATION_DURATION = 300; - - private static final Pools.Pool sTabPool = new Pools.SynchronizedPool<>(16); - - /** - * Scrollable tabs display a subset of tabs at any given moment, and can contain longer tab - * labels and a larger number of tabs. They are best used for browsing contexts in touch - * interfaces when users don’t need to directly compare the tab labels. - * - * @see #setTabMode(int) - * @see #getTabMode() - */ - public static final int MODE_SCROLLABLE = 0; - - /** - * Fixed tabs display all tabs concurrently and are best used with content that benefits from - * quick pivots between tabs. The maximum number of tabs is limited by the view’s width. - * Fixed tabs have equal width, based on the widest tab label. - * - * @see #setTabMode(int) - * @see #getTabMode() - */ - public static final int MODE_FIXED = 1; - - /** - * @hide - */ - @RestrictTo(LIBRARY_GROUP) - @IntDef(value = {MODE_SCROLLABLE, MODE_FIXED}) - @Retention(RetentionPolicy.SOURCE) - public @interface Mode {} - - /** - * Gravity used to fill the {@link TabLayout} as much as possible. This option only takes effect - * when used with {@link #MODE_FIXED}. - * - * @see #setTabGravity(int) - * @see #getTabGravity() - */ - public static final int GRAVITY_FILL = 0; - - /** - * Gravity used to lay out the tabs in the center of the {@link TabLayout}. - * - * @see #setTabGravity(int) - * @see #getTabGravity() - */ - public static final int GRAVITY_CENTER = 1; - - /** - * @hide - */ - @RestrictTo(LIBRARY_GROUP) - @IntDef(flag = true, value = {GRAVITY_FILL, GRAVITY_CENTER}) - @Retention(RetentionPolicy.SOURCE) - public @interface TabGravity {} - - /** - * Callback interface invoked when a tab's selection state changes. - */ - public interface OnTabSelectedListener { - - /** - * Called when a tab enters the selected state. - * - * @param tab The tab that was selected - */ - public void onTabSelected(Tab tab); - - /** - * Called when a tab exits the selected state. - * - * @param tab The tab that was unselected - */ - public void onTabUnselected(Tab tab); - - /** - * Called when a tab that is already selected is chosen again by the user. Some applications - * may use this action to return to the top level of a category. - * - * @param tab The tab that was reselected. - */ - public void onTabReselected(Tab tab); - } - - private final ArrayList mTabs = new ArrayList<>(); - private Tab mSelectedTab; - - private final ScrollView mTopScrollView; - private final SlidingTabStrip mTabStrip; - - int mTabPaddingStart; - int mTabPaddingTop; - int mTabPaddingEnd; - int mTabPaddingBottom; - - int mTabTextAppearance; - ColorStateList mTabTextColors; - float mTabTextSize; - float mTabTextMultiLineSize; - - final int mTabBackgroundResId; - - int mTabMaxWidth = Integer.MAX_VALUE; - private final int mRequestedTabMinWidth; - private final int mRequestedTabMaxWidth; - private final int mScrollableTabMinWidth; - - private int mContentInsetStart; - - int mTabGravity; - int mMode; - - private OnTabSelectedListener mSelectedListener; - private final ArrayList mSelectedListeners = new ArrayList<>(); - private OnTabSelectedListener mCurrentVpSelectedListener; - - private ValueAnimator mScrollAnimator; - - ViewPager mViewPager; - private ViewPagerAdapter mPagerAdapter; - private DataSetObserver mPagerAdapterObserver; - private VerticalTabLayoutOnPageChangeListener mPageChangeListener; - private AdapterChangeListener mAdapterChangeListener; - private boolean mSetupViewPagerImplicitly; - - // Pool we use as a simple RecyclerBin - private final Pools.Pool mTabViewPool = new Pools.SimplePool<>(12); - - public VerticalTabLayout(Context context) { - this(context, null); - } - - public VerticalTabLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public VerticalTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - //ndroidx.appcompat.widget.ThemeUtils.checkAppCompatTheme(context); - - setOrientation(VERTICAL); - - // Disable the Scroll Bar - // setVerticalScrollBarEnabled(false); - - LayoutParams scrollViewParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - scrollViewParams.weight = 1f; - mTopScrollView = new ScrollView(context); - mTopScrollView.setLayoutParams(scrollViewParams); - - // Add the TabStrip - mTabStrip = new SlidingTabStrip(context); - mTopScrollView.addView(mTabStrip, 0, new ScrollView.LayoutParams( - LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)); - super.addView(mTopScrollView); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout, - defStyleAttr, R.style.Widget_Design_TabLayout); - - mTabStrip.setSelectedIndicatorHeight( - a.getDimensionPixelSize(R.styleable.TabLayout_tabIndicatorHeight, 0)); - mTabStrip.setSelectedIndicatorColor(a.getColor(R.styleable.TabLayout_tabIndicatorColor, 0)); - - mTabPaddingStart = mTabPaddingTop = mTabPaddingEnd = mTabPaddingBottom = a - .getDimensionPixelSize(R.styleable.TabLayout_tabPadding, 0); - mTabPaddingStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingStart, - mTabPaddingStart); - mTabPaddingTop = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingTop, - mTabPaddingTop); - mTabPaddingEnd = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingEnd, - mTabPaddingEnd); - mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingBottom, - mTabPaddingBottom); - - mTabTextAppearance = a.getResourceId(R.styleable.TabLayout_tabTextAppearance, - R.style.TextAppearance_Design_Tab); - - // Text colors/sizes come from the text appearance first - final TypedArray ta = context.obtainStyledAttributes(mTabTextAppearance, - androidx.appcompat.R.styleable.TextAppearance); - try { - mTabTextSize = ta.getDimensionPixelSize( - androidx.appcompat.R.styleable.TextAppearance_android_textSize, 0); - mTabTextColors = ta.getColorStateList( - androidx.appcompat.R.styleable.TextAppearance_android_textColor); - } finally { - ta.recycle(); - } - - if (a.hasValue(R.styleable.TabLayout_tabTextColor)) { - // If we have an explicit text color set, use it instead - mTabTextColors = a.getColorStateList(R.styleable.TabLayout_tabTextColor); - } - - if (a.hasValue(R.styleable.TabLayout_tabSelectedTextColor)) { - // We have an explicit selected text color set, so we need to make merge it with the - // current colors. This is exposed so that developers can use theme attributes to set - // this (theme attrs in ColorStateLists are Lollipop+) - final int selected = a.getColor(R.styleable.TabLayout_tabSelectedTextColor, 0); - mTabTextColors = createColorStateList(mTabTextColors.getDefaultColor(), selected); - } - - mRequestedTabMinWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMinWidth, - INVALID_WIDTH); - mRequestedTabMaxWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMaxWidth, - INVALID_WIDTH); - mTabBackgroundResId = a.getResourceId(R.styleable.TabLayout_tabBackground, 0); - mContentInsetStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabContentStart, 0); - mMode = a.getInt(R.styleable.TabLayout_tabMode, MODE_FIXED); - mTabGravity = a.getInt(R.styleable.TabLayout_tabGravity, GRAVITY_FILL); - a.recycle(); - - // TODO add attr for these - final Resources res = getResources(); - mTabTextMultiLineSize = res.getDimensionPixelSize(R.dimen.design_tab_text_size_2line); - mScrollableTabMinWidth = res.getDimensionPixelSize(R.dimen.design_tab_scrollable_min_width); - - // Now apply the tab mode and gravity - applyModeAndGravity(); - } - - /** - * Sets the tab indicator's color for the currently selected tab. - * - * @param color color to use for the indicator - * - * @attr ref android.support.design.R.styleable#TabLayout_tabIndicatorColor - */ - public void setSelectedTabIndicatorColor(@ColorInt int color) { - mTabStrip.setSelectedIndicatorColor(color); - } - - /** - * Sets the tab indicator's height for the currently selected tab. - * - * @param height height to use for the indicator in pixels - * - * @attr ref android.support.design.R.styleable#TabLayout_tabIndicatorHeight - */ - public void setSelectedTabIndicatorHeight(int height) { - mTabStrip.setSelectedIndicatorHeight(height); - } - - /** - * Set the scroll position of the tabs. This is useful for when the tabs are being displayed as - * part of a scrolling container such as {@link ViewPager}. - *

- * Calling this method does not update the selected tab, it is only used for drawing purposes. - * - * @param position current scroll position - * @param positionOffset Value from [0, 1) indicating the offset from {@code position}. - * @param updateSelectedText Whether to update the text's selected state. - */ - public void setScrollPosition(int position, float positionOffset, boolean updateSelectedText) { - setScrollPosition(position, positionOffset, updateSelectedText, true); - } - - void setScrollPosition(int position, float positionOffset, boolean updateSelectedText, - boolean updateIndicatorPosition) { - final int roundedPosition = Math.round(position + positionOffset); - if (roundedPosition < 0 || roundedPosition >= mTabStrip.getChildCount()) { - return; - } - - // Set the indicator position, if enabled - if (updateIndicatorPosition) { - mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset); - } - - // Now update the scroll position, canceling any running animation - if (mScrollAnimator != null && mScrollAnimator.isRunning()) { - mScrollAnimator.cancel(); - } - mTopScrollView.scrollTo(calculateScrollXForTab(position, positionOffset), 0); - - // Update the 'selected state' view as we scroll, if enabled - if (updateSelectedText) { - setSelectedTabView(roundedPosition); - } - } - - private float getScrollPosition() { - return mTabStrip.getIndicatorPosition(); - } - - public void setLastTabAsBottom() { - final int position = mTabs.size() - 1; - final int selectedTabPosition = mSelectedTab != null ? mSelectedTab.getPosition() : 0; - final TabView view = (TabView) mTabStrip.getChildAt(position); - view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); - mTabStrip.removeViewAt(position); - requestLayout(); - - mTabs.remove(position); - super.addView(view); - - final int newTabCount = mTabs.size(); - for (int i = position; i < newTabCount; i++) { - mTabs.get(i).setPosition(i); - } - - if (selectedTabPosition == position) { - selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1))); - } - } - - /** - * Add a tab to this layout. The tab will be added at the end of the list. - * If this is the first tab to be added it will become the selected tab. - * - * @param tab Tab to add - */ - public void addTab(@NonNull Tab tab) { - addTab(tab, mTabs.isEmpty()); - } - - /** - * Add a tab to this layout. The tab will be inserted at position. - * If this is the first tab to be added it will become the selected tab. - * - * @param tab The tab to add - * @param position The new position of the tab - */ - public void addTab(@NonNull Tab tab, int position) { - addTab(tab, position, mTabs.isEmpty()); - } - - /** - * Add a tab to this layout. The tab will be added at the end of the list. - * - * @param tab Tab to add - * @param setSelected True if the added tab should become the selected tab. - */ - public void addTab(@NonNull Tab tab, boolean setSelected) { - addTab(tab, mTabs.size(), setSelected); - } - - /** - * Add a tab to this layout. The tab will be inserted at position. - * - * @param tab The tab to add - * @param position The new position of the tab - * @param setSelected True if the added tab should become the selected tab. - */ - public void addTab(@NonNull Tab tab, int position, boolean setSelected) { - if (tab.mParent != this) { - throw new IllegalArgumentException("Tab belongs to a different TabLayout."); - } - configureTab(tab, position); - addTabView(tab); - - if (setSelected) { - tab.select(); - } - } - - private void addTabFromItemView(@NonNull TabItem item) { - - final Tab tab = newTab(); - if (item.text != null) { - tab.setText(item.text); - } - if (item.icon != null) { - tab.setIcon(item.icon); - } - if (item.customLayout != 0) { - tab.setCustomView(item.customLayout); - } - if (!TextUtils.isEmpty(item.getContentDescription())) { - tab.setContentDescription(item.getContentDescription()); - } - addTab(tab); - } - - /** - * @deprecated Use {@link #addOnTabSelectedListener(OnTabSelectedListener)} and - * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}. - */ - @Deprecated - public void setOnTabSelectedListener(@Nullable OnTabSelectedListener listener) { - // The logic in this method emulates what we had before support for multiple - // registered listeners. - if (mSelectedListener != null) { - removeOnTabSelectedListener(mSelectedListener); - } - // Update the deprecated field so that we can remove the passed listener the next - // time we're called - mSelectedListener = listener; - if (listener != null) { - addOnTabSelectedListener(listener); - } - } - - /** - * Add a {@link TabLayout.OnTabSelectedListener} that will be invoked when tab selection - * changes. - * - *

Components that add a listener should take care to remove it when finished via - * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}.

- * - * @param listener listener to add - */ - public void addOnTabSelectedListener(@NonNull OnTabSelectedListener listener) { - if (!mSelectedListeners.contains(listener)) { - mSelectedListeners.add(listener); - } - } - - /** - * Remove the given {@link TabLayout.OnTabSelectedListener} that was previously added via - * {@link #addOnTabSelectedListener(OnTabSelectedListener)}. - * - * @param listener listener to remove - */ - public void removeOnTabSelectedListener(@NonNull OnTabSelectedListener listener) { - mSelectedListeners.remove(listener); - } - - /** - * Remove all previously added {@link TabLayout.OnTabSelectedListener}s. - */ - public void clearOnTabSelectedListeners() { - mSelectedListeners.clear(); - } - - /** - * Create and return a new {@link Tab}. You need to manually add this using - * {@link #addTab(Tab)} or a related method. - * - * @return A new Tab - * @see #addTab(Tab) - */ - @NonNull - public Tab newTab() { - Tab tab = sTabPool.acquire(); - if (tab == null) { - tab = new Tab(); - } - tab.mParent = this; - tab.mView = createTabView(tab); - return tab; - } - - /** - * Returns the number of tabs currently registered with the action bar. - * - * @return Tab count - */ - public int getTabCount() { - return mTabs.size(); - } - - /** - * Returns the tab at the specified index. - */ - @Nullable - public Tab getTabAt(int index) { - return (index < 0 || index >= getTabCount()) ? null : mTabs.get(index); - } - - /** - * Returns the position of the current selected tab. - * - * @return selected tab position, or {@code -1} if there isn't a selected tab. - */ - public int getSelectedTabPosition() { - return mSelectedTab != null ? mSelectedTab.getPosition() : -1; - } - - /** - * Remove a tab from the layout. If the removed tab was selected it will be deselected - * and another tab will be selected if present. - * - * @param tab The tab to remove - */ - public void removeTab(Tab tab) { - if (tab.mParent != this) { - throw new IllegalArgumentException("Tab does not belong to this TabLayout."); - } - - removeTabAt(tab.getPosition()); - } - - /** - * Remove a tab from the layout. If the removed tab was selected it will be deselected - * and another tab will be selected if present. - * - * @param position Position of the tab to remove - */ - public void removeTabAt(int position) { - final int selectedTabPosition = mSelectedTab != null ? mSelectedTab.getPosition() : 0; - removeTabViewAt(position); - - final Tab removedTab = mTabs.remove(position); - if (removedTab != null) { - removedTab.reset(); - sTabPool.release(removedTab); - } - - final int newTabCount = mTabs.size(); - for (int i = position; i < newTabCount; i++) { - mTabs.get(i).setPosition(i); - } - - if (selectedTabPosition == position) { - selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1))); - } - } - - /** - * Remove all tabs from the action bar and deselect the current tab. - */ - public void removeAllTabs() { - // Remove all the views - for (int i = mTabStrip.getChildCount() - 1; i >= 0; i--) { - removeTabViewAt(i); - } - - for (final Iterator i = mTabs.iterator(); i.hasNext();) { - final Tab tab = i.next(); - i.remove(); - tab.reset(); - sTabPool.release(tab); - } - - mSelectedTab = null; - } - - /** - * Set the behavior mode for the Tabs in this layout. The valid input options are: - *
    - *
  • {@link #MODE_FIXED}: Fixed tabs display all tabs concurrently and are best used - * with content that benefits from quick pivots between tabs.
  • - *
  • {@link #MODE_SCROLLABLE}: Scrollable tabs display a subset of tabs at any given moment, - * and can contain longer tab labels and a larger number of tabs. They are best used for - * browsing contexts in touch interfaces when users don’t need to directly compare the tab - * labels. This mode is commonly used with a {@link ViewPager}.
  • - *
- * - * @param mode one of {@link #MODE_FIXED} or {@link #MODE_SCROLLABLE}. - * - * @attr ref android.support.design.R.styleable#TabLayout_tabMode - */ - public void setTabMode(@Mode int mode) { - if (mode != mMode) { - mMode = mode; - applyModeAndGravity(); - } - } - - /** - * Returns the current mode used by this {@link TabLayout}. - * - * @see #setTabMode(int) - */ - @Mode - public int getTabMode() { - return mMode; - } - - /** - * Set the gravity to use when laying out the tabs. - * - * @param gravity one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}. - * - * @attr ref android.support.design.R.styleable#TabLayout_tabGravity - */ - public void setTabGravity(@TabGravity int gravity) { - if (mTabGravity != gravity) { - mTabGravity = gravity; - applyModeAndGravity(); - } - } - - /** - * The current gravity used for laying out tabs. - * - * @return one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}. - */ - @TabGravity - public int getTabGravity() { - return mTabGravity; - } - - /** - * Sets the text colors for the different states (normal, selected) used for the tabs. - * - * @see #getTabTextColors() - */ - public void setTabTextColors(@Nullable ColorStateList textColor) { - if (mTabTextColors != textColor) { - mTabTextColors = textColor; - updateAllTabs(); - } - } - - /** - * Gets the text colors for the different states (normal, selected) used for the tabs. - */ - @Nullable - public ColorStateList getTabTextColors() { - return mTabTextColors; - } - - /** - * Sets the text colors for the different states (normal, selected) used for the tabs. - * - * @attr ref android.support.design.R.styleable#TabLayout_tabTextColor - * @attr ref android.support.design.R.styleable#TabLayout_tabSelectedTextColor - */ - public void setTabTextColors(int normalColor, int selectedColor) { - setTabTextColors(createColorStateList(normalColor, selectedColor)); - } - - /** - * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}. - * - *

This is the same as calling {@link #setupWithViewPager(ViewPager, boolean)} with - * auto-refresh enabled.

- * - * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link - */ - public void setupWithViewPager(@Nullable ViewPager viewPager) { - setupWithViewPager(viewPager, true); - } - - /** - * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}. - * - *

This method will link the given ViewPager and this VerticalTabLayout together so that - * changes in one are automatically reflected in the other. This includes scroll state changes - * and clicks. The tabs displayed in this layout will be populated - * from the ViewPager adapter's page titles.

- * - *

If {@code autoRefresh} is {@code true}, any changes in the {@link PagerAdapter} will - * trigger this layout to re-populate itself from the adapter's titles.

- * - *

If the given ViewPager is non-null, it needs to already have a - * {@link PagerAdapter} set.

- * - * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link - * @param autoRefresh whether this layout should refresh its contents if the given ViewPager's - * content changes - */ - public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh) { - setupWithViewPager(viewPager, autoRefresh, false); - } - - private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh, - boolean implicitSetup) { - if (mViewPager != null) { - // If we've already been setup with a ViewPager, remove us from it - if (mPageChangeListener != null) { - mViewPager.removeOnPageChangeListener(mPageChangeListener); - } - if (mAdapterChangeListener != null) { - mViewPager.removeOnAdapterChangeListener(mAdapterChangeListener); - } - } - - if (mCurrentVpSelectedListener != null) { - // If we already have a tab selected listener for the ViewPager, remove it - removeOnTabSelectedListener(mCurrentVpSelectedListener); - mCurrentVpSelectedListener = null; - } - - if (viewPager != null) { - mViewPager = viewPager; - - // Add our custom OnPageChangeListener to the ViewPager - if (mPageChangeListener == null) { - mPageChangeListener = new VerticalTabLayoutOnPageChangeListener(this); - } - mPageChangeListener.reset(); - viewPager.addOnPageChangeListener(mPageChangeListener); - - // Now we'll add a tab selected listener to set ViewPager's current item - mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager); - addOnTabSelectedListener(mCurrentVpSelectedListener); - - final ViewPagerAdapter adapter = (VerticalTabLayout.ViewPagerAdapter) viewPager.getAdapter(); - if (adapter != null) { - // Now we'll populate ourselves from the pager adapter, adding an observer if - // autoRefresh is enabled - setPagerAdapter(adapter, autoRefresh); - } - - // Add a listener so that we're notified of any adapter changes - if (mAdapterChangeListener == null) { - mAdapterChangeListener = new AdapterChangeListener(); - } - mAdapterChangeListener.setAutoRefresh(autoRefresh); - viewPager.addOnAdapterChangeListener(mAdapterChangeListener); - - // Now update the scroll position to match the ViewPager's current item - setScrollPosition(viewPager.getCurrentItem(), 0f, true); - } else { - // We've been given a null ViewPager so we need to clear out the internal state, - // listeners and observers - mViewPager = null; - setPagerAdapter(null, false); - } - - mSetupViewPagerImplicitly = implicitSetup; - } - - /** - * @deprecated Use {@link #setupWithViewPager(ViewPager)} to link a VerticalTabLayout with a ViewPager - * together. When that method is used, the VerticalTabLayout will be automatically updated - * when the {@link PagerAdapter} is changed. - */ - @Deprecated - public void setTabsFromPagerAdapter(@Nullable final ViewPagerAdapter adapter) { - setPagerAdapter(adapter, false); - } - - @Override - public boolean shouldDelayChildPressedState() { - // Only delay the pressed state if the tabs can scroll - return getTabScrollRange() > 0; - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - if (mViewPager == null) { - // If we don't have a ViewPager already, check if our parent is a ViewPager to - // setup with it automatically - final ViewParent vp = getParent(); - if (vp instanceof ViewPager) { - // If we have a ViewPager parent and we've been added as part of its decor, let's - // assume that we should automatically setup to display any titles - setupWithViewPager((ViewPager) vp, true, true); - } - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - if (mSetupViewPagerImplicitly) { - // If we've been setup with a ViewPager implicitly, let's clear out any listeners, etc - setupWithViewPager(null); - mSetupViewPagerImplicitly = false; - } - } - - private int getTabScrollRange() { - return Math.max(0, mTabStrip.getWidth() - mTopScrollView.getWidth() - mTopScrollView.getPaddingLeft() - - mTopScrollView.getPaddingRight()); - } - - void setPagerAdapter(@Nullable final ViewPagerAdapter adapter, final boolean addObserver) { - if (mPagerAdapter != null && mPagerAdapterObserver != null) { - // If we already have a PagerAdapter, unregister our observer - mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver); - } - - mPagerAdapter = adapter; - - if (addObserver && adapter != null) { - // Register our observer on the new adapter - if (mPagerAdapterObserver == null) { - mPagerAdapterObserver = new PagerAdapterObserver(); - } - adapter.registerDataSetObserver(mPagerAdapterObserver); - } - - // Finally make sure we reflect the new adapter - populateFromPagerAdapter(); - } - - void populateFromPagerAdapter() { - removeAllTabs(); - - if (mPagerAdapter != null) { - final int adapterCount = mPagerAdapter.getCount(); - for (int i = 0; i < adapterCount; i++) { - addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false); - if (mPagerAdapter.getIcon(i) != 0) { - getTabAt(i).setIcon(mPagerAdapter.getIcon(i)); - } - } - - // Make sure we reflect the currently set ViewPager item - if (mViewPager != null && adapterCount > 0) { - final int curItem = mViewPager.getCurrentItem(); - if (curItem != getSelectedTabPosition() && curItem < getTabCount()) { - selectTab(getTabAt(curItem)); - } - } - } - } - - private void updateAllTabs() { - for (int i = 0, z = mTabs.size(); i < z; i++) { - mTabs.get(i).updateView(); - } - } - - private TabView createTabView(@NonNull final Tab tab) { - TabView tabView = mTabViewPool != null ? mTabViewPool.acquire() : null; - if (tabView == null) { - tabView = new TabView(getContext()); - } - tabView.setTab(tab); - tabView.setFocusable(true); - tabView.setMinimumWidth(getTabMinWidth()); - return tabView; - } - - private void configureTab(Tab tab, int position) { - tab.setPosition(position); - mTabs.add(position, tab); - - final int count = mTabs.size(); - for (int i = position + 1; i < count; i++) { - mTabs.get(i).setPosition(i); - } - } - - private void addTabView(Tab tab) { - final TabView tabView = tab.mView; - mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs()); - } - - // @Override - public void addViewItem(View child) { - addViewInternal(child); - } - - // @Override - public void addViewItem(View child, int index) { - addViewInternal(child); - } - - // @Override - public void addViewItem(View child, ViewGroup.LayoutParams params) { - addViewInternal(child); - } - - // @Override - public void addViewItem(View child, int index, ViewGroup.LayoutParams params) { - addViewInternal(child); - } - - private void addViewInternal(final View child) { - if (child instanceof TabItem) { - addTabFromItemView((TabItem) child); - } else { - throw new IllegalArgumentException("Only TabItem instances can be added to TabLayout"); - } - } - - private LinearLayout.LayoutParams createLayoutParamsForTabs() { - final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); - updateTabViewLayoutParams(lp); - return lp; - } - - private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) { - if (mMode == MODE_FIXED && mTabGravity == GRAVITY_FILL) { - lp.width = 0; - lp.weight = 1; - } else { - lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; - lp.weight = 0; - } - } - - int dpToPx(int dps) { - return Math.round(getResources().getDisplayMetrics().density * dps); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // If we have a MeasureSpec which allows us to decide our height, try and use the default - // height - final int idealHeight = dpToPx(getDefaultHeight()) + mTopScrollView.getPaddingTop() + mTopScrollView.getPaddingBottom(); - - switch (MeasureSpec.getMode(heightMeasureSpec)) { - case MeasureSpec.AT_MOST: - heightMeasureSpec = MeasureSpec.makeMeasureSpec( - Math.min(idealHeight, MeasureSpec.getSize(heightMeasureSpec)), - MeasureSpec.EXACTLY); - break; - case 0 /* MeasureSpec.UNSPECIFIED */: - heightMeasureSpec = MeasureSpec.makeMeasureSpec(idealHeight, MeasureSpec.EXACTLY); - break; - } - - final int specWidth = MeasureSpec.getSize(widthMeasureSpec); - if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) { - // If we don't have an unspecified width spec, use the given size to calculate - // the max tab width - mTabMaxWidth = mRequestedTabMaxWidth > 0 - ? mRequestedTabMaxWidth - : specWidth - dpToPx(TAB_MIN_WIDTH_MARGIN); - } - - // Now super measure itself using the (possibly) modified height spec - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (getChildCount() == 1) { - // If we're in fixed mode then we need to make the tab strip is the same width as us - // so we don't scroll - final View child = getChildAt(0); - boolean remeasure = false; - - switch (mMode) { - case MODE_SCROLLABLE: - // We only need to resize the child if it's smaller than us. This is similar - // to fillViewport - remeasure = child.getMeasuredWidth() < mTopScrollView.getMeasuredWidth(); - break; - case MODE_FIXED: - // Resize the child so that it doesn't scroll - remeasure = child.getMeasuredWidth() != mTopScrollView.getMeasuredWidth(); - break; - } - - if (remeasure) { - // Re-measure the child with a widthSpec set to be exactly our measure width - int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, mTopScrollView.getPaddingTop() - + mTopScrollView.getPaddingBottom(), child.getLayoutParams().height); - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - mTopScrollView.getMeasuredWidth(), MeasureSpec.EXACTLY); - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - } - } - } - - private void removeTabViewAt(int position) { - final TabView view = (TabView) mTabStrip.getChildAt(position); - mTabStrip.removeViewAt(position); - if (view != null) { - view.reset(); - mTabViewPool.release(view); - } - requestLayout(); - } - - private void animateToTab(int newPosition) { - if (newPosition == Tab.INVALID_POSITION) { - return; - } - - if (getWindowToken() == null || !ViewCompat.isLaidOut(this) - || mTabStrip.childrenNeedLayout()) { - // If we don't have a window token, or we haven't been laid out yet just draw the new - // position now - setScrollPosition(newPosition, 0f, true); - return; - } - - final int startScrollX = mTopScrollView.getScrollX(); - final int targetScrollX = calculateScrollXForTab(newPosition, 0); - - if (startScrollX != targetScrollX) { - ensureScrollAnimator(); - - mScrollAnimator.setIntValues(startScrollX, targetScrollX); - mScrollAnimator.start(); - } - - // Now animate the indicator - mTabStrip.animateIndicatorToPosition(newPosition, ANIMATION_DURATION); - } - - private void ensureScrollAnimator() { - if (mScrollAnimator == null) { - mScrollAnimator = new ValueAnimator(); - mScrollAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); - mScrollAnimator.setDuration(ANIMATION_DURATION); - mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - scrollTo((int) animator.getAnimatedValue(), 0); - } - }); - } - } - - void setScrollAnimatorListener(Animator.AnimatorListener listener) { - ensureScrollAnimator(); - mScrollAnimator.addListener(listener); - } - - private void setSelectedTabView(int position) { - final int tabCount = mTabStrip.getChildCount(); - if (position < tabCount) { - for (int i = 0; i < tabCount; i++) { - final View child = mTabStrip.getChildAt(i); - child.setSelected(i == position); - } - } - } - - void selectTab(Tab tab) { - selectTab(tab, true); - } - - void selectTab(final Tab tab, boolean updateIndicator) { - final Tab currentTab = mSelectedTab; - - if (currentTab == tab) { - if (currentTab != null) { - dispatchTabReselected(tab); - animateToTab(tab.getPosition()); - } - } else { - final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION; - if (updateIndicator) { - if ((currentTab == null || currentTab.getPosition() == Tab.INVALID_POSITION) - && newPosition != Tab.INVALID_POSITION) { - // If we don't currently have a tab, just draw the indicator - setScrollPosition(newPosition, 0f, true); - } else { - animateToTab(newPosition); - } - if (newPosition != Tab.INVALID_POSITION) { - setSelectedTabView(newPosition); - } - } - if (currentTab != null) { - dispatchTabUnselected(currentTab); - } - mSelectedTab = tab; - if (tab != null) { - dispatchTabSelected(tab); - } - } - } - - private void dispatchTabSelected(@NonNull final Tab tab) { - for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { - mSelectedListeners.get(i).onTabSelected(tab); - } - } - - private void dispatchTabUnselected(@NonNull final Tab tab) { - for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { - mSelectedListeners.get(i).onTabUnselected(tab); - } - } - - private void dispatchTabReselected(@NonNull final Tab tab) { - for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { - mSelectedListeners.get(i).onTabReselected(tab); - } - } - - private int calculateScrollXForTab(int position, float positionOffset) { - if (mMode == MODE_SCROLLABLE) { - final View selectedChild = mTabStrip.getChildAt(position); - final View nextChild = position + 1 < mTabStrip.getChildCount() - ? mTabStrip.getChildAt(position + 1) - : null; - final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0; - final int nextWidth = nextChild != null ? nextChild.getWidth() : 0; - - // base scroll amount: places center of tab in center of parent - int scrollBase = selectedChild.getLeft() + (selectedWidth / 2) - (mTopScrollView.getWidth() / 2); - // offset amount: fraction of the distance between centers of tabs - int scrollOffset = (int) ((selectedWidth + nextWidth) * 0.5f * positionOffset); - - return (ViewCompat.getLayoutDirection(mTopScrollView) == ViewCompat.LAYOUT_DIRECTION_LTR) - ? scrollBase + scrollOffset - : scrollBase - scrollOffset; - } - return 0; - } - - private void applyModeAndGravity() { - int paddingStart = 0; - if (mMode == MODE_SCROLLABLE) { - // If we're scrollable, or fixed at start, inset using padding - paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart); - } - ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0); - - switch (mMode) { - case MODE_FIXED: - mTabStrip.setGravity(Gravity.CENTER_VERTICAL); - break; - case MODE_SCROLLABLE: - mTabStrip.setGravity(GravityCompat.START); - break; - } - - updateTabViews(true); - } - - void updateTabViews(final boolean requestLayout) { - for (int i = 0; i < mTabStrip.getChildCount(); i++) { - View child = mTabStrip.getChildAt(i); - child.setMinimumWidth(getTabMinWidth()); - updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams()); - if (requestLayout) { - child.requestLayout(); - } - } - } - - /** - * A tab in this layout. Instances can be created via {@link #newTab()}. - */ - public static final class Tab { - - /** - * An invalid position for a tab. - * - * @see #getPosition() - */ - public static final int INVALID_POSITION = -1; - - private Object mTag; - private Drawable mIcon; - private CharSequence mText; - private CharSequence mContentDesc; - private int mPosition = INVALID_POSITION; - private View mCustomView; - - VerticalTabLayout mParent; - TabView mView; - - Tab() { - // Private constructor - } - - /** - * @return This Tab's tag object. - */ - @Nullable - public Object getTag() { - return mTag; - } - - /** - * Give this Tab an arbitrary object to hold for later use. - * - * @param tag Object to store - * @return The current instance for call chaining - */ - @NonNull - public Tab setTag(@Nullable Object tag) { - mTag = tag; - return this; - } - - - /** - * Returns the custom view used for this tab. - * - * @see #setCustomView(View) - * @see #setCustomView(int) - */ - @Nullable - public View getCustomView() { - return mCustomView; - } - - /** - * Set a custom view to be used for this tab. - *

- * If the provided view contains a {@link TextView} with an ID of - * {@link android.R.id#text1} then that will be updated with the value given - * to {@link #setText(CharSequence)}. Similarly, if this layout contains an - * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with - * the value given to {@link #setIcon(Drawable)}. - *

- * - * @param view Custom view to be used as a tab. - * @return The current instance for call chaining - */ - @NonNull - public Tab setCustomView(@Nullable View view) { - mCustomView = view; - updateView(); - return this; - } - - /** - * Set a custom view to be used for this tab. - *

- * If the inflated layout contains a {@link TextView} with an ID of - * {@link android.R.id#text1} then that will be updated with the value given - * to {@link #setText(CharSequence)}. Similarly, if this layout contains an - * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with - * the value given to {@link #setIcon(Drawable)}. - *

- * - * @param resId A layout resource to inflate and use as a custom tab view - * @return The current instance for call chaining - */ - @NonNull - public Tab setCustomView(@LayoutRes int resId) { - final LayoutInflater inflater = LayoutInflater.from(mView.getContext()); - return setCustomView(inflater.inflate(resId, mView, false)); - } - - /** - * Return the icon associated with this tab. - * - * @return The tab's icon - */ - @Nullable - public Drawable getIcon() { - return mIcon; - } - - /** - * Return the current position of this tab in the action bar. - * - * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in - * the action bar. - */ - public int getPosition() { - return mPosition; - } - - void setPosition(int position) { - mPosition = position; - } - - /** - * Return the text of this tab. - * - * @return The tab's text - */ - @Nullable - public CharSequence getText() { - return mText; - } - - /** - * Set the icon displayed on this tab. - * - * @param icon The drawable to use as an icon - * @return The current instance for call chaining - */ - @NonNull - public Tab setIcon(@Nullable Drawable icon) { - mIcon = icon; - updateView(); - return this; - } - - /** - * Set the icon displayed on this tab. - * - * @param resId A resource ID referring to the icon that should be displayed - * @return The current instance for call chaining - */ - @NonNull - public Tab setIcon(@DrawableRes int resId) { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return setIcon(resId == 0 ? null : AppCompatResources.getDrawable(mParent.getContext(), resId)); - } - - /** - * Set the text displayed on this tab. Text may be truncated if there is not room to display - * the entire string. - * - * @param text The text to display - * @return The current instance for call chaining - */ - @NonNull - public Tab setText(@Nullable CharSequence text) { - mText = text; - updateView(); - return this; - } - - /** - * Set the text displayed on this tab. Text may be truncated if there is not room to display - * the entire string. - * - * @param resId A resource ID referring to the text that should be displayed - * @return The current instance for call chaining - */ - @NonNull - public Tab setText(@StringRes int resId) { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return setText(mParent.getResources().getText(resId)); - } - - /** - * Select this tab. Only valid if the tab has been added to the action bar. - */ - public void select() { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - mParent.selectTab(this); - } - - /** - * Returns true if this tab is currently selected. - */ - public boolean isSelected() { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return mParent.getSelectedTabPosition() == mPosition; - } - - /** - * Set a description of this tab's content for use in accessibility support. If no content - * description is provided the title will be used. - * - * @param resId A resource ID referring to the description text - * @return The current instance for call chaining - * @see #setContentDescription(CharSequence) - * @see #getContentDescription() - */ - @NonNull - public Tab setContentDescription(@StringRes int resId) { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return setContentDescription(mParent.getResources().getText(resId)); - } - - /** - * Set a description of this tab's content for use in accessibility support. If no content - * description is provided the title will be used. - * - * @param contentDesc Description of this tab's content - * @return The current instance for call chaining - * @see #setContentDescription(int) - * @see #getContentDescription() - */ - @NonNull - public Tab setContentDescription(@Nullable CharSequence contentDesc) { - mContentDesc = contentDesc; - updateView(); - return this; - } - - /** - * Gets a brief description of this tab's content for use in accessibility support. - * - * @return Description of this tab's content - * @see #setContentDescription(CharSequence) - * @see #setContentDescription(int) - */ - @Nullable - public CharSequence getContentDescription() { - return mContentDesc; - } - - void updateView() { - if (mView != null) { - mView.update(); - } - } - - void reset() { - mParent = null; - mView = null; - mTag = null; - mIcon = null; - mText = null; - mContentDesc = null; - mPosition = INVALID_POSITION; - mCustomView = null; - } - } - - class TabView extends LinearLayout { - private Tab mTab; - private TextView mTextView; - private ImageView mIconView; - - private View mCustomView; - private TextView mCustomTextView; - private ImageView mCustomIconView; - - private int mDefaultMaxLines = 2; - - public TabView(Context context) { - super(context); - if (mTabBackgroundResId != 0) { - ViewCompat.setBackground( - this, AppCompatResources.getDrawable(context, mTabBackgroundResId)); - } - ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop, - mTabPaddingEnd, mTabPaddingBottom); - setGravity(Gravity.CENTER_VERTICAL); - setOrientation(HORIZONTAL); - setClickable(true); - ViewCompat.setPointerIcon(this, - PointerIconCompat.getSystemIcon(getContext(), PointerIconCompat.TYPE_HAND)); - } - - @Override - public boolean performClick() { - final boolean handled = super.performClick(); - - if (mTab != null) { - if (!handled) { - playSoundEffect(SoundEffectConstants.CLICK); - } - mTab.select(); - return true; - } else { - return handled; - } - } - - @Override - public void setSelected(final boolean selected) { - final boolean changed = isSelected() != selected; - - super.setSelected(selected); - - if (changed && selected && Build.VERSION.SDK_INT < 16) { - // Pre-JB we need to manually send the TYPE_VIEW_SELECTED event - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - } - - // Always dispatch this to the child views, regardless of whether the value has - // changed - if (mTextView != null) { - mTextView.setSelected(selected); - } - if (mIconView != null) { - mIconView.setSelected(selected); - } - if (mCustomView != null) { - mCustomView.setSelected(selected); - } - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - // This view masquerades as an action bar tab. - event.setClassName(ActionBar.Tab.class.getName()); - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - // This view masquerades as an action bar tab. - info.setClassName(ActionBar.Tab.class.getName()); - } - - @Override - public void onMeasure(final int origWidthMeasureSpec, final int origHeightMeasureSpec) { - final int specWidthSize = MeasureSpec.getSize(origWidthMeasureSpec); - final int specWidthMode = MeasureSpec.getMode(origWidthMeasureSpec); - final int maxWidth = getTabMaxWidth(); - - final int widthMeasureSpec; - final int heightMeasureSpec = origHeightMeasureSpec; - - if (maxWidth > 0 && (specWidthMode == MeasureSpec.UNSPECIFIED - || specWidthSize > maxWidth)) { - // If we have a max width and a given spec which is either unspecified or - // larger than the max width, update the width spec using the same mode - widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTabMaxWidth, MeasureSpec.AT_MOST); - } else { - // Else, use the original width spec - widthMeasureSpec = origWidthMeasureSpec; - } - - // Now lets measure - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - // We need to switch the text size based on whether the text is spanning 2 lines or not - if (mTextView != null) { - // final Resources res = getResources(); - float textSize = mTabTextSize; - int maxLines = mDefaultMaxLines; - - if (mIconView != null && mIconView.getVisibility() == VISIBLE) { - // If the icon view is being displayed, we limit the text to 1 line - maxLines = 2; - } else if (mTextView != null && mTextView.getLineCount() > 1) { - // Otherwise when we have text which wraps we reduce the text size - textSize = mTabTextMultiLineSize; - } - - final float curTextSize = mTextView.getTextSize(); - final int curLineCount = mTextView.getLineCount(); - final int curMaxLines = TextViewCompat.getMaxLines(mTextView); - - if (textSize != curTextSize || (curMaxLines >= 0 && maxLines != curMaxLines)) { - // We've got a new text size and/or max lines... - boolean updateTextView = true; - - if (mMode == MODE_FIXED && textSize > curTextSize && curLineCount == 1) { - // If we're in fixed mode, going up in text size and currently have 1 line - // then it's very easy to get into an infinite recursion. - // To combat that we check to see if the change in text size - // will cause a line count change. If so, abort the size change and stick - // to the smaller size. - final Layout layout = mTextView.getLayout(); - if (layout == null || approximateLineWidth(layout, 0, textSize) - > getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) { - updateTextView = false; - } - } - - if (updateTextView) { - mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); - mTextView.setMaxLines(maxLines); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - } - } - - void setTab(@Nullable final Tab tab) { - if (tab != mTab) { - mTab = tab; - update(); - } - } - - void reset() { - setTab(null); - setSelected(false); - } - - final void update() { - final Tab tab = mTab; - final View custom = tab != null ? tab.getCustomView() : null; - if (custom != null) { - final ViewParent customParent = custom.getParent(); - if (customParent != this) { - if (customParent != null) { - ((ViewGroup) customParent).removeView(custom); - } - addView(custom); - } - mCustomView = custom; - if (mTextView != null) { - mTextView.setVisibility(GONE); - } - if (mIconView != null) { - mIconView.setVisibility(GONE); - mIconView.setImageDrawable(null); - } - - mCustomTextView = (TextView) custom.findViewById(android.R.id.text1); - if (mCustomTextView != null) { - mDefaultMaxLines = TextViewCompat.getMaxLines(mCustomTextView); - } - mCustomIconView = (ImageView) custom.findViewById(android.R.id.icon); - } else { - // We do not have a custom view. Remove one if it already exists - if (mCustomView != null) { - removeView(mCustomView); - mCustomView = null; - } - mCustomTextView = null; - mCustomIconView = null; - } - - if (mCustomView == null) { - // If there isn't a custom view, we'll us our own in-built layouts - if (mIconView == null) { - ImageView iconView = (ImageView) LayoutInflater.from(getContext()) - .inflate(R.layout.design_layout_tab_icon, this, false); - iconView.setLayoutParams(new LinearLayout.LayoutParams(dpToPx(25), - dpToPx(25))); - addView(iconView, 0); - mIconView = iconView; - } - if (mTextView == null) { - TextView textView = (TextView) LayoutInflater.from(getContext()) - .inflate(R.layout.design_layout_tab_text, this, false); - LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT); - textParams.gravity = Gravity.CENTER_VERTICAL; - textView.setLayoutParams(textParams); - addView(textView); - mTextView = textView; - mDefaultMaxLines = TextViewCompat.getMaxLines(mTextView); - } - TextViewCompat.setTextAppearance(mTextView, mTabTextAppearance); - if (mTabTextColors != null) { - mTextView.setTextColor(mTabTextColors); - } - updateTextAndIcon(mTextView, mIconView); - } else { - // Else, we'll see if there is a TextView or ImageView present and update them - if (mCustomTextView != null || mCustomIconView != null) { - updateTextAndIcon(mCustomTextView, mCustomIconView); - } - } - - // Finally update our selected state - setSelected(tab != null && tab.isSelected()); - } - - private void updateTextAndIcon(@Nullable final TextView textView, - @Nullable final ImageView iconView) { - final Drawable icon = mTab != null ? mTab.getIcon() : null; - final CharSequence text = mTab != null ? mTab.getText() : null; - final CharSequence contentDesc = mTab != null ? mTab.getContentDescription() : null; - - if (iconView != null) { - if (icon != null) { - iconView.setImageDrawable(icon); - iconView.setVisibility(VISIBLE); - setVisibility(VISIBLE); - } else { - iconView.setVisibility(GONE); - iconView.setImageDrawable(null); - } - iconView.setContentDescription(contentDesc); - } - - final boolean hasText = !TextUtils.isEmpty(text); - if (textView != null) { - if (hasText) { - textView.setText(text); - textView.setVisibility(VISIBLE); - setVisibility(VISIBLE); - } else { - textView.setVisibility(GONE); - textView.setText(null); - } - textView.setContentDescription(contentDesc); - } - - if (iconView != null) { - MarginLayoutParams lp = ((MarginLayoutParams) iconView.getLayoutParams()); - int bottomMargin = 0; - if (hasText && iconView.getVisibility() == VISIBLE) { - // If we're showing both text and icon, add some margin bottom to the icon - bottomMargin = dpToPx(DEFAULT_GAP_TEXT_ICON); - } - if (bottomMargin != lp.bottomMargin) { - lp.bottomMargin = bottomMargin; - iconView.requestLayout(); - } - } - TooltipCompat.setTooltipText(this, hasText ? null : contentDesc); - } - - public Tab getTab() { - return mTab; - } - - /** - * Approximates a given lines width with the new provided text size. - */ - private float approximateLineWidth(Layout layout, int line, float textSize) { - return layout.getLineWidth(line) * (textSize / layout.getPaint().getTextSize()); - } - } - - private class SlidingTabStrip extends LinearLayout { - private int mSelectedIndicatorHeight; - private final Paint mSelectedIndicatorPaint; - - int mSelectedPosition = -1; - float mSelectionOffset; - - private int mLayoutDirection = -1; - - private int mIndicatorTop = -1; - private int mIndicatorBottom = -1; - - private ValueAnimator mIndicatorAnimator; - - SlidingTabStrip(Context context) { - super(context); - setWillNotDraw(false); - - - // Easy way to get vertical tab view - setOrientation(VERTICAL); - - mSelectedIndicatorPaint = new Paint(); - } - - void setSelectedIndicatorColor(int color) { - if (mSelectedIndicatorPaint.getColor() != color) { - mSelectedIndicatorPaint.setColor(color); - ViewCompat.postInvalidateOnAnimation(this); - } - } - - void setSelectedIndicatorHeight(int height) { - if (mSelectedIndicatorHeight != height) { - mSelectedIndicatorHeight = height; - ViewCompat.postInvalidateOnAnimation(this); - } - } - - boolean childrenNeedLayout() { - for (int i = 0, z = getChildCount(); i < z; i++) { - final View child = getChildAt(i); - if (child.getWidth() <= 0) { - return true; - } - } - return false; - } - - void setIndicatorPositionFromTabPosition(int position, float positionOffset) { - if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { - mIndicatorAnimator.cancel(); - } - - mSelectedPosition = position; - mSelectionOffset = positionOffset; - updateIndicatorPosition(); - } - - float getIndicatorPosition() { - return mSelectedPosition + mSelectionOffset; - } - - @Override - public void onRtlPropertiesChanged(int layoutDirection) { - super.onRtlPropertiesChanged(layoutDirection); - - // Workaround for a bug before Android M where LinearLayout did not relayout itself when - // layout direction changed. - if (Build.VERSION.SDK_INT < 23 /* Build.VERSION_CODES.M */) { - //noinspection WrongConstant - if (mLayoutDirection != layoutDirection) { - requestLayout(); - mLayoutDirection = layoutDirection; - } - } - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) { - // ScrollView will first measure use with UNSPECIFIED, and then with - // EXACTLY. Ignore the first call since anything we do will be overwritten anyway - return; - } - - if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) { - final int count = getChildCount(); - - // First we'll find the widest tab - int largestTabWidth = 0; - for (int i = 0, z = count; i < z; i++) { - View child = getChildAt(i); - if (child.getVisibility() == VISIBLE) { - largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth()); - } - } - - if (largestTabWidth <= 0) { - // If we don't have a largest child yet, skip until the next measure pass - return; - } - - final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN); - boolean remeasure = false; - - if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) { - // If the tabs fit within our width minus gutters, we will set all tabs to have - // the same width - for (int i = 0; i < count; i++) { - final LinearLayout.LayoutParams lp = - (LayoutParams) getChildAt(i).getLayoutParams(); - if (lp.width != largestTabWidth || lp.weight != 0) { - lp.width = largestTabWidth; - lp.weight = 0; - remeasure = true; - } - } - } else { - // If the tabs will wrap to be larger than the width minus gutters, we need - // to switch to GRAVITY_FILL - mTabGravity = GRAVITY_FILL; - updateTabViews(false); - remeasure = true; - } - - if (remeasure) { - // Now re-measure after our changes - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - - if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { - // If we're currently running an animation, lets cancel it and start a - // new animation with the remaining duration - mIndicatorAnimator.cancel(); - final long duration = mIndicatorAnimator.getDuration(); - animateIndicatorToPosition(mSelectedPosition, - Math.round((1f - mIndicatorAnimator.getAnimatedFraction()) * duration)); - } else { - // If we've been layed out, update the indicator position - updateIndicatorPosition(); - } - } - - private void updateIndicatorPosition() { - final View selectedTitle = getChildAt(mSelectedPosition); - int top, bottom; - - if (selectedTitle != null && selectedTitle.getWidth() > 0) { - top = selectedTitle.getTop(); - bottom = selectedTitle.getBottom(); - - if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) { - // Draw the selection partway between the tabs - View nextTitle = getChildAt(mSelectedPosition + 1); - top = (int) (mSelectionOffset * nextTitle.getTop() + - (1.0f - mSelectionOffset) * top); - bottom = (int) (mSelectionOffset * nextTitle.getBottom() + - (1.0f - mSelectionOffset) * bottom); - } - } else { - top = bottom = -1; - } - - setIndicatorPosition(top, bottom); - } - - void setIndicatorPosition(int top, int bottom) { - if (top != mIndicatorTop || bottom != mIndicatorBottom) { - // If the indicator's left/right has changed, invalidate - mIndicatorTop = top; - mIndicatorBottom = bottom; - ViewCompat.postInvalidateOnAnimation(this); - } - } - - void animateIndicatorToPosition(final int position, int duration) { - if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { - mIndicatorAnimator.cancel(); - } - - final View targetView = getChildAt(position); - if (targetView == null) { - // If we don't have a view, just update the position now and return - updateIndicatorPosition(); - return; - } - - final int targetTop = targetView.getTop(); - final int targetBottom = targetView.getBottom(); - final int startTop; - final int startBottom; - - if (Math.abs(position - mSelectedPosition) <= 1) { - // If the views are adjacent, we'll animate from edge-to-edge - startTop = mIndicatorTop; - startBottom = mIndicatorBottom; - } else { - // Else, we'll just grow from the nearest edge - final int offset = dpToPx(MOTION_NON_ADJACENT_OFFSET); - if (position < mSelectedPosition) { - // We're going end-to-start - /* - if (isRtl) { - startTop = startBottom = targetTop - offset; - } else { - */ - startTop = startBottom = targetBottom + offset; - // } - } else { - // We're going start-to-end - /* - if (isRtl) { - startLeft = startRight = targetRight + offset; - } else { - */ - startTop = startBottom = targetTop - offset; - // } - } - } - - if (startTop != targetTop || startBottom != targetBottom) { - ValueAnimator animator = mIndicatorAnimator = new ValueAnimator(); - animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); - animator.setDuration(duration); - animator.setFloatValues(0, 1); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @SuppressLint("RestrictedApi") - @Override - public void onAnimationUpdate(ValueAnimator animator) { - final float fraction = animator.getAnimatedFraction(); - setIndicatorPosition( - AnimationUtils.lerp(startTop, targetTop, fraction), - AnimationUtils.lerp(startBottom, targetBottom, fraction)); - } - }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - mSelectedPosition = position; - mSelectionOffset = 0f; - } - }); - animator.start(); - } - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - - // Thick colored line at the left side of the current selection - if (mIndicatorTop >= 0 && mIndicatorBottom > mIndicatorTop) { - canvas.drawRect(0, mIndicatorTop, - mSelectedIndicatorHeight, mIndicatorBottom, mSelectedIndicatorPaint); - } - } - } - - private static ColorStateList createColorStateList(int defaultColor, int selectedColor) { - final int[][] states = new int[2][]; - final int[] colors = new int[2]; - int i = 0; - - states[i] = SELECTED_STATE_SET; - colors[i] = selectedColor; - i++; - - // Default enabled state - states[i] = EMPTY_STATE_SET; - colors[i] = defaultColor; - i++; - - return new ColorStateList(states, colors); - } - - private int getDefaultHeight() { - boolean hasIconAndText = false; - for (int i = 0, count = mTabs.size(); i < count; i++) { - Tab tab = mTabs.get(i); - if (tab != null && tab.getIcon() != null && !TextUtils.isEmpty(tab.getText())) { - hasIconAndText = true; - break; - } - } - return hasIconAndText ? DEFAULT_HEIGHT_WITH_TEXT_ICON : DEFAULT_HEIGHT; - } - - private int getTabMinWidth() { - if (mRequestedTabMinWidth != INVALID_WIDTH) { - // If we have been given a min width, use it - return mRequestedTabMinWidth; - } - // Else, we'll use the default value - return mMode == MODE_SCROLLABLE ? mScrollableTabMinWidth : 0; - } - - @Override - public LayoutParams generateLayoutParams(AttributeSet attrs) { - // We don't care about the layout params of any views added to us, since we don't actually - // add them. The only view we add is the SlidingTabStrip, which is done manually. - // We return the default layout params so that we don't blow up if we're given a TabItem - // without android:layout_* values. - return generateDefaultLayoutParams(); - } - - int getTabMaxWidth() { - return mTabMaxWidth; - } - - /** - * A {@link ViewPager.OnPageChangeListener} class which contains the - * necessary calls back to the provided {@link TabLayout} so that the tab position is - * kept in sync. - * - *

This class stores the provided VerticalTabLayout weakly, meaning that you can use - * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener) - * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and - * not cause a leak. - */ - public static class VerticalTabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener { - private final WeakReference mTabLayoutRef; - private int mPreviousScrollState; - private int mScrollState; - - public VerticalTabLayoutOnPageChangeListener(VerticalTabLayout tabLayout) { - mTabLayoutRef = new WeakReference<>(tabLayout); - } - - @Override - public void onPageScrollStateChanged(final int state) { - mPreviousScrollState = mScrollState; - mScrollState = state; - } - - @Override - public void onPageScrolled(final int position, final float positionOffset, - final int positionOffsetPixels) { - final VerticalTabLayout tabLayout = mTabLayoutRef.get(); - if (tabLayout != null) { - // Only update the text selection if we're not settling, or we are settling after - // being dragged - final boolean updateText = mScrollState != SCROLL_STATE_SETTLING || - mPreviousScrollState == SCROLL_STATE_DRAGGING; - // Update the indicator if we're not settling after being idle. This is caused - // from a setCurrentItem() call and will be handled by an animation from - // onPageSelected() instead. - final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING - && mPreviousScrollState == SCROLL_STATE_IDLE); - tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator); - } - } - - @Override - public void onPageSelected(final int position) { - final VerticalTabLayout tabLayout = mTabLayoutRef.get(); - if (tabLayout != null && tabLayout.getSelectedTabPosition() != position - && position < tabLayout.getTabCount()) { - // Select the tab, only updating the indicator if we're not being dragged/settled - // (since onPageScrolled will handle that). - final boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE - || (mScrollState == SCROLL_STATE_SETTLING - && mPreviousScrollState == SCROLL_STATE_IDLE); - tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator); - } - } - - void reset() { - mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE; - } - } - - /** - * A {@link TabLayout.OnTabSelectedListener} class which contains the necessary calls back - * to the provided {@link ViewPager} so that the tab position is kept in sync. - */ - public static class ViewPagerOnTabSelectedListener implements VerticalTabLayout.OnTabSelectedListener { - private final ViewPager mViewPager; - - public ViewPagerOnTabSelectedListener(ViewPager viewPager) { - mViewPager = viewPager; - } - - @Override - public void onTabSelected(VerticalTabLayout.Tab tab) { - mViewPager.setCurrentItem(tab.getPosition()); - } - - @Override - public void onTabUnselected(VerticalTabLayout.Tab tab) { - // No-op - } - - @Override - public void onTabReselected(VerticalTabLayout.Tab tab) { - // No-op - } - } - - private class PagerAdapterObserver extends DataSetObserver { - PagerAdapterObserver() { - } - - @Override - public void onChanged() { - populateFromPagerAdapter(); - } - - @Override - public void onInvalidated() { - populateFromPagerAdapter(); - } - } - - private class AdapterChangeListener implements ViewPager.OnAdapterChangeListener { - private boolean mAutoRefresh; - - AdapterChangeListener() { - } - - @Override - public void onAdapterChanged(@NonNull ViewPager viewPager, - @Nullable PagerAdapter oldAdapter, @Nullable PagerAdapter newAdapter) { - if (mViewPager == viewPager) { - setPagerAdapter((ViewPagerAdapter) newAdapter, mAutoRefresh); - } - } - - void setAutoRefresh(boolean autoRefresh) { - mAutoRefresh = autoRefresh; - } - } - - public static class ViewPagerAdapter extends FragmentPagerAdapter { - List viewPagerList = new ArrayList<>(); - - public ViewPagerAdapter(FragmentManager fragmentManager) { - super(fragmentManager); - } - - @Override - public Fragment getItem(int position) { - return viewPagerList.get(position).fragment; - } - - @Override - public int getCount() { - return viewPagerList.size(); - } - - public int getIcon(int position) { - return viewPagerList.get(position).icon; - } - - @Override - public CharSequence getPageTitle(int position) { - return viewPagerList.get(position).title; - } - - public void addFragment(Fragment fragment, int icon, String name) { - ViewPagerItem item = new ViewPagerItem(); - item.fragment = fragment; - item.icon = icon; - item.title = name; - viewPagerList.add(item); - } - - public void setFragment(int index, Fragment fragment, int icon, String name) { - ViewPagerItem item = new ViewPagerItem(); - item.fragment = fragment; - item.icon = icon; - item.title = name; - viewPagerList.set(index, item); - } - - public void removeFragment(int index) { - viewPagerList.remove(index); - } - } - - public static class ViewPagerItem { - public Fragment fragment; - public String title; - public int icon; - } -} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseLauncherActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseLauncherActivity.java index 064be72987..668050da2e 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseLauncherActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseLauncherActivity.java @@ -1,5 +1,7 @@ package net.kdt.pojavlaunch; +import static net.kdt.pojavlaunch.Tools.getFileName; + import android.app.*; import android.content.*; import android.database.Cursor; @@ -28,12 +30,10 @@ public abstract class BaseLauncherActivity extends BaseActivity { public Button mPlayButton; - public ConsoleFragment mConsoleView; - public CrashFragment mCrashView; public ProgressBar mLaunchProgress; public Spinner mVersionSelector; public MultiRTConfigDialog mRuntimeConfigDialog; - public TextView mLaunchTextStatus, mTextVersion; + public TextView mLaunchTextStatus; public JMinecraftVersionList mVersionList; public MinecraftDownloaderTask mTask; @@ -45,52 +45,24 @@ public abstract class BaseLauncherActivity extends BaseActivity { public abstract void statusIsLaunching(boolean isLaunching); - public void mcaccLogout(View view) { - //PojavProfile.reset(); - finish(); - } - - public void launcherMenu(View view) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.mcl_options); - builder.setItems(R.array.mcl_options, new DialogInterface.OnClickListener(){ + /** + * Used by the custom control button from the layout_main_v4 + * @param view The view triggering the function + */ + public void launchCustomControlsActivity(View view){ + startActivity(new Intent(BaseLauncherActivity.this, CustomControlsActivity.class)); + } - @Override - public void onClick(DialogInterface p1, int p2) - { - switch (p2) { - case 0: // Mod installer - installMod(false); - break; - case 1: // Mod installer with java args - installMod(true); - break; - case 2: // Custom controls - startActivity(new Intent(BaseLauncherActivity.this, CustomControlsActivity.class)); - break; - case 3: { // About - final AlertDialog.Builder aboutB = new AlertDialog.Builder(BaseLauncherActivity.this); - aboutB.setTitle(R.string.mcl_option_about); - try { - aboutB.setMessage(Html.fromHtml(String.format(Tools.read(getAssets().open("about_en.txt")), - Tools.APP_NAME, - BuildConfig.VERSION_NAME, - "3.2.3") - )); - } catch (Exception e) { - throw new RuntimeException(e); - } - aboutB.setPositiveButton(android.R.string.ok, null); - AlertDialog aboutDialog = aboutB.show(); - TextView aboutTv = aboutDialog.findViewById(android.R.id.message); - aboutTv.setMovementMethod(LinkMovementMethod.getInstance()); - } break; - } - } - }); - builder.show(); + /** + * Used by the install button from the layout_main_v4 + * @param view The view triggering the function + */ + public void installJarFile(View view){ + installMod(false); } + + public static final int RUN_MOD_INSTALLER = 2050; private void installMod(boolean customJavaArgs) { if (customJavaArgs) { @@ -101,15 +73,12 @@ private void installMod(boolean customJavaArgs) { final EditText edit = new EditText(this); edit.setSingleLine(); edit.setHint("-jar/-cp /path/to/file.jar ..."); - builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener(){ - @Override - public void onClick(DialogInterface di, int i) { - Intent intent = new Intent(BaseLauncherActivity.this, JavaGUILauncherActivity.class); - intent.putExtra("skipDetectMod", true); - intent.putExtra("javaArgs", edit.getText().toString()); - startActivity(intent); - } - }); + builder.setPositiveButton(android.R.string.ok, (di, i) -> { + Intent intent = new Intent(BaseLauncherActivity.this, JavaGUILauncherActivity.class); + intent.putExtra("skipDetectMod", true); + intent.putExtra("javaArgs", edit.getText().toString()); + startActivity(intent); + }); dialog = builder.create(); dialog.setView(edit); dialog.show(); @@ -132,7 +101,7 @@ public void launchGame(View v) { v.setEnabled(false); mTask = new MinecraftDownloaderTask(this); mTask.execute(mProfile.selectedVersion); - mCrashView.resetCrashLog = true; + } } @@ -160,19 +129,17 @@ protected void onResume(){ decorView.setSystemUiVisibility(uiOptions); System.out.println("call to onResume; E"); } + SharedPreferences.OnSharedPreferenceChangeListener listRefreshListener = null; @Override protected void onResumeFragments() { super.onResumeFragments(); if(listRefreshListener == null) { final BaseLauncherActivity thiz = this; - listRefreshListener = new SharedPreferences.OnSharedPreferenceChangeListener() { - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if(key.startsWith("vertype_")) { - System.out.println("Verlist update needed!"); - new RefreshVersionListTask(thiz).execute(); - } + listRefreshListener = (sharedPreferences, key) -> { + if(key.startsWith("vertype_")) { + System.out.println("Verlist update needed!"); + new RefreshVersionListTask(thiz).execute(); } }; } @@ -181,76 +148,11 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin System.out.println("call to onResumeFragments"); mRuntimeConfigDialog = new MultiRTConfigDialog(); mRuntimeConfigDialog.prepare(this); - try{ - final ProgressDialog barrier = new ProgressDialog(this); - barrier.setMessage(getString(R.string.global_waiting)); - barrier.setProgressStyle(barrier.STYLE_SPINNER); - barrier.setCancelable(false); - barrier.show(); - - new Thread(new Runnable(){ - - @Override - public void run() - { - while (mConsoleView == null) { - try { - Thread.sleep(20); - } catch (Throwable th) {} - } - - try { - Thread.sleep(100); - } catch (Throwable th) {} - - runOnUiThread(new Runnable() { - @Override - public void run() - { - try { - mConsoleView.putLog(""); - barrier.dismiss(); - } catch (Throwable th) { - startActivity(getIntent()); - finish(); - } - } - }); - } - }).start(); - File lastCrashFile = Tools.lastFileModified(Tools.DIR_HOME_CRASH); - if(CrashFragment.isNewCrash(lastCrashFile) || !mCrashView.getLastCrash().isEmpty()){ - mCrashView.resetCrashLog = false; - initTabs(2); - - } /*else throw new Exception();*/ - } catch(Throwable e) { - e.printStackTrace(); - } + //TODO ADD CRASH CHECK AND FOCUS System.out.println("call to onResumeFragments; E"); } - public static String getFileName(Context ctx, Uri uri) { - String result = null; - if (uri.getScheme().equals("content")) { - Cursor cursor = ctx.getContentResolver().query(uri, null, null, null, null); - try { - if (cursor != null && cursor.moveToFirst()) { - result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); - } - } finally { - cursor.close(); - } - } - if (result == null) { - result = uri.getPath(); - int cut = result.lastIndexOf('/'); - if (cut != -1) { - result = result.substring(cut + 1); - } - } - return result; - } + @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode,resultCode,data); @@ -260,62 +162,58 @@ protected void onActivityResult(int requestCode, int resultCode, @Nullable Inten barrier.setProgressStyle(barrier.STYLE_SPINNER); barrier.setCancelable(false); barrier.show(); + + // Install the runtime if (requestCode == MultiRTConfigDialog.MULTIRT_PICK_RUNTIME) { - if (data != null) { - final Uri uri = data.getData(); - Thread t = new Thread(() -> { - try { - String name = getFileName(this, uri); - MultiRTUtils.installRuntimeNamed(getContentResolver().openInputStream(uri), name, - (resid, stuff) -> BaseLauncherActivity.this.runOnUiThread( - () -> barrier.setMessage(BaseLauncherActivity.this.getString(resid, stuff)))); - MultiRTUtils.postPrepare(BaseLauncherActivity.this, name); - } catch (IOException e) { - Tools.showError(BaseLauncherActivity.this - , e); - } - BaseLauncherActivity.this.runOnUiThread(new Runnable() { - @Override - public void run() { - barrier.dismiss(); - mRuntimeConfigDialog.refresh(); - mRuntimeConfigDialog.dialog.show(); - } - }); - }); - t.start(); - } - } else if (requestCode == RUN_MOD_INSTALLER) { - if (data != null) { - final Uri uri = data.getData(); - barrier.setMessage(BaseLauncherActivity.this.getString(R.string.multirt_progress_caching)); - Thread t = new Thread(()->{ - try { - final String name = getFileName(this, uri); - final File modInstallerFile = new File(getCacheDir(), name); - FileOutputStream fos = new FileOutputStream(modInstallerFile); - IOUtils.copy(getContentResolver().openInputStream(uri), fos); - fos.close(); - BaseLauncherActivity.this.runOnUiThread(() -> { - barrier.dismiss(); - Intent intent = new Intent(BaseLauncherActivity.this, JavaGUILauncherActivity.class); - intent.putExtra("modFile", modInstallerFile); - startActivity(intent); - }); - }catch(IOException e) { - Tools.showError(BaseLauncherActivity.this,e); - } + if (data == null) return; + + final Uri uri = data.getData(); + Thread t = new Thread(() -> { + try { + String name = getFileName(this, uri); + MultiRTUtils.installRuntimeNamed(getContentResolver().openInputStream(uri), name, + (resid, stuff) -> BaseLauncherActivity.this.runOnUiThread( + () -> barrier.setMessage(BaseLauncherActivity.this.getString(resid, stuff)))); + MultiRTUtils.postPrepare(BaseLauncherActivity.this, name); + } catch (IOException e) { + Tools.showError(BaseLauncherActivity.this, e); + } + BaseLauncherActivity.this.runOnUiThread(() -> { + barrier.dismiss(); + mRuntimeConfigDialog.refresh(); + mRuntimeConfigDialog.dialog.show(); }); - t.start(); - } + }); + t.start(); } - } - } - // Catching touch exception - @Override - public boolean onTouchEvent(MotionEvent event) { - return super.onTouchEvent(event); + // Run a mod installer + if (requestCode == RUN_MOD_INSTALLER) { + if (data == null) return; + + final Uri uri = data.getData(); + barrier.setMessage(BaseLauncherActivity.this.getString(R.string.multirt_progress_caching)); + Thread t = new Thread(()->{ + try { + final String name = getFileName(this, uri); + final File modInstallerFile = new File(getCacheDir(), name); + FileOutputStream fos = new FileOutputStream(modInstallerFile); + IOUtils.copy(getContentResolver().openInputStream(uri), fos); + fos.close(); + BaseLauncherActivity.this.runOnUiThread(() -> { + barrier.dismiss(); + Intent intent = new Intent(BaseLauncherActivity.this, JavaGUILauncherActivity.class); + intent.putExtra("modFile", modInstallerFile); + startActivity(intent); + }); + }catch(IOException e) { + Tools.showError(BaseLauncherActivity.this,e); + } + }); + t.start(); + } + + } } protected abstract void initTabs(int pageIndex); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseMainActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseMainActivity.java index c7fc40c1bb..fa1fbeed15 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseMainActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseMainActivity.java @@ -48,8 +48,9 @@ public class BaseMainActivity extends LoggableActivity { public float scaleFactor = 1; public double sensitivityFactor; private final int fingerStillThreshold = (int) Tools.dpToPx(9); + private final int fingerScrollThreshold = (int) Tools.dpToPx(6); private float initialX, initialY; - private int scrollInitialX, scrollInitialY; + private float scrollLastInitialX, scrollLastInitialY; private float prevX, prevY; private int currentPointerID; @@ -62,8 +63,8 @@ public void handleMessage(Message msg) { switch (msg.what) { case MSG_LEFT_MOUSE_BUTTON_CHECK: if(LauncherPreferences.PREF_DISABLE_GESTURES) break; - int x = CallbackBridge.mouseX; - int y = CallbackBridge.mouseY; + float x = CallbackBridge.mouseX; + float y = CallbackBridge.mouseY; if (CallbackBridge.isGrabbing() && Math.abs(initialX - x) < fingerStillThreshold && Math.abs(initialY - y) < fingerStillThreshold) { @@ -96,8 +97,9 @@ public void handleMessage(Message msg) { private TextView textLog; private ScrollView contentScroll; private ToggleButton toggleLog; - private GestureDetector gestureDetector; - private DoubleTapDetector doubleTapDetector; + + private TapDetector singleTapDetector; + private TapDetector doubleTapDetector; private TextView debugText; private NavigationView.OnNavigationItemSelectedListener gameActionListener; @@ -164,8 +166,9 @@ protected void initLayout(int resId) { System.out.println("WidthHeight: " + windowWidth + ":" + windowHeight); - gestureDetector = new GestureDetector(this, new SingleTapConfirm()); - doubleTapDetector = new DoubleTapDetector(); + + singleTapDetector = new TapDetector(1, TapDetector.DETECTION_METHOD_BOTH); + doubleTapDetector = new TapDetector(2, TapDetector.DETECTION_METHOD_DOWN); // Menu @@ -262,7 +265,7 @@ protected void initLayout(int resId) { float mouseX = mousePointer.getX(); float mouseY = mousePointer.getY(); - if (gestureDetector.onTouchEvent(event)) { + if (singleTapDetector.onTouchEvent(event)) { mouse_x = (mouseX * scaleFactor); mouse_y = (mouseY * scaleFactor); CallbackBridge.sendCursorPos(mouse_x, mouse_y); @@ -273,8 +276,8 @@ protected void initLayout(int resId) { } else { switch (action) { case MotionEvent.ACTION_POINTER_DOWN: // 5 - scrollInitialX = CallbackBridge.mouseX; - scrollInitialY = CallbackBridge.mouseY; + scrollLastInitialX = event.getX(); + scrollLastInitialY = event.getY(); break; case MotionEvent.ACTION_DOWN: @@ -285,10 +288,15 @@ protected void initLayout(int resId) { case MotionEvent.ACTION_MOVE: // 2 - if (!CallbackBridge.isGrabbing() && event.getPointerCount() == 2 && !LauncherPreferences.PREF_DISABLE_GESTURES) { //Scrolling feature - CallbackBridge.sendScroll( Tools.pxToDp(CallbackBridge.mouseX - scrollInitialX)/30, Tools.pxToDp(CallbackBridge.mouseY - scrollInitialY)/30); - scrollInitialX = CallbackBridge.mouseX; - scrollInitialY = CallbackBridge.mouseY; + if (!CallbackBridge.isGrabbing() && event.getPointerCount() >= 2 && !LauncherPreferences.PREF_DISABLE_GESTURES) { //Scrolling feature + int hScroll = ((int) (event.getX() - scrollLastInitialX)) / fingerScrollThreshold; + int vScroll = ((int) (event.getY() - scrollLastInitialY)) / fingerScrollThreshold; + + if(vScroll != 0 || hScroll != 0){ + CallbackBridge.sendScroll(hScroll, vScroll); + scrollLastInitialX = event.getX(); + scrollLastInitialY = event.getY(); + } } else { if(currentPointerID == event.getPointerId(0)) { mouseX = Math.max(0, Math.min(displayMetrics.widthPixels, mouseX + (x - prevX) * LauncherPreferences.PREF_MOUSESPEED)); @@ -354,7 +362,7 @@ public boolean onTouch(View p1, MotionEvent e) { mouse_x = (e.getX() * scaleFactor); mouse_y = (e.getY() * scaleFactor); //One android click = one MC click - if(gestureDetector.onTouchEvent(e)){ + if(singleTapDetector.onTouchEvent(e)){ CallbackBridge.putMouseEventWithCoords(rightOverride ? (byte) 1 : (byte) 0, (int)mouse_x, (int)mouse_y); return true; } @@ -424,8 +432,8 @@ public boolean onTouch(View p1, MotionEvent e) { break; case MotionEvent.ACTION_POINTER_DOWN: // 5 - scrollInitialX = CallbackBridge.mouseX; - scrollInitialY = CallbackBridge.mouseY; + scrollLastInitialX = e.getX(); + scrollLastInitialY = e.getY(); //Checking if we are pressing the hotbar to select the item hudKeyHandled = handleGuiBar((int)e.getX(e.getPointerCount()-1), (int) e.getY(e.getPointerCount()-1)); if(hudKeyHandled != -1){ @@ -440,10 +448,17 @@ public boolean onTouch(View p1, MotionEvent e) { break; case MotionEvent.ACTION_MOVE: - if (!CallbackBridge.isGrabbing() && e.getPointerCount() == 2 && !LauncherPreferences.PREF_DISABLE_GESTURES) { //Scrolling feature - CallbackBridge.sendScroll(Tools.pxToDp(mouse_x - scrollInitialX)/30 , Tools.pxToDp(mouse_y - scrollInitialY)/30); - scrollInitialX = (int)mouse_x; - scrollInitialY = (int)mouse_y; + if (!CallbackBridge.isGrabbing() && e.getPointerCount() >= 2 && !LauncherPreferences.PREF_DISABLE_GESTURES) { //Scrolling feature + int hScroll = ((int) (e.getX() - scrollLastInitialX)) / fingerScrollThreshold; + int vScroll = ((int) (e.getY() - scrollLastInitialY)) / fingerScrollThreshold; + + if(vScroll != 0 || hScroll != 0){ + CallbackBridge.sendScroll(hScroll, vScroll); + scrollLastInitialX = e.getX(); + scrollLastInitialY = e.getY(); + } + + } else if (!CallbackBridge.isGrabbing() && e.getPointerCount() == 1) { //Touch hover CallbackBridge.sendCursorPos(mouse_x, mouse_y); prevX = e.getX(); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/DoubleTapDetector.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/DoubleTapDetector.java deleted file mode 100644 index e87c893f9f..0000000000 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/DoubleTapDetector.java +++ /dev/null @@ -1,67 +0,0 @@ -package net.kdt.pojavlaunch; - -import android.view.MotionEvent; - -import static android.view.MotionEvent.ACTION_DOWN; -import static android.view.MotionEvent.ACTION_POINTER_DOWN; - -/** - * Class aiming at better detecting double tap events for EVERY POINTER - * Only uses the least amount of events possible, - * since we aren't guaranteed to have all events in order - */ -public class DoubleTapDetector { - - private final static int DOUBLE_TAP_MIN_DELTA_MS = 50; - private final static int DOUBLE_TAP_MAX_DELTA_MS = 300; - private final static int DOUBLE_TAP_SLOP_SQUARE_PX = (int) Math.pow(Tools.dpToPx(100), 2); - - private long mLastEventTime = 0; - private float mLastX = 9999; - private float mLastY = 9999; - - /** - * A function to call when you have a touch event. - * @param e The MotionEvent to inspect - * @return whether or not a double tap happened for a pointer - */ - public boolean onTouchEvent(MotionEvent e){ - int eventAction = e.getActionMasked(); - int pointerIndex; - - //Get the pointer index we want to look at - if(eventAction == ACTION_DOWN) pointerIndex = 0; - else if(eventAction == ACTION_POINTER_DOWN) pointerIndex = e.getActionIndex(); - else return false; - - float eventX = e.getX(pointerIndex); - float eventY = e.getY(pointerIndex); - long eventTime = e.getEventTime(); - - long deltaTime = eventTime - mLastEventTime; - if(deltaTime > DOUBLE_TAP_MIN_DELTA_MS && deltaTime < DOUBLE_TAP_MAX_DELTA_MS){ - int deltaX = (int) mLastX - (int) eventX; - int deltaY = (int) mLastY - (int) eventY; - if((deltaX*deltaX + deltaY*deltaY) < DOUBLE_TAP_SLOP_SQUARE_PX){ - //Then I guess there is a double tap :thonk: - resetDoubleTapState(); - return true; - } - } - - mLastEventTime = eventTime; - mLastX = eventX; - mLastY = eventY; - return false; - } - - /** - * Reset the double tap values. - */ - private void resetDoubleTapState(){ - mLastEventTime = 0; - mLastX = 9999; - mLastY = 9999; - } - -} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java index 5a7ab0bbda..ee80acb16e 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java @@ -49,8 +49,8 @@ public class JavaGUILauncherActivity extends LoggableActivity implements View.On public void handleMessage(Message msg) { switch (msg.what) { case MSG_LEFT_MOUSE_BUTTON_CHECK: { - int x = CallbackBridge.mouseX; - int y = CallbackBridge.mouseY; + float x = CallbackBridge.mouseX; + float y = CallbackBridge.mouseY; if (CallbackBridge.isGrabbing() && Math.abs(initialX - x) < fingerStillThreshold && Math.abs(initialY - y) < fingerStillThreshold) { diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLauncherActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLauncherActivity.java index bc6037f5dd..7f95c8bec7 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLauncherActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLauncherActivity.java @@ -1,7 +1,11 @@ package net.kdt.pojavlaunch; +import static android.os.Build.VERSION_CODES.P; +import static net.kdt.pojavlaunch.Tools.ignoreNotch; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_HIDE_SIDEBAR; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE; + import android.animation.ValueAnimator; -import android.content.SharedPreferences; import android.content.res.Configuration; import android.graphics.Color; import android.graphics.Typeface; @@ -9,126 +13,140 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.support.design.widget.VerticalTabLayout.ViewPagerAdapter; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; -import android.widget.ProgressBar; +import android.widget.ImageView; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; - import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.Guideline; -import androidx.viewpager.widget.ViewPager; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; +import net.kdt.pojavlaunch.extra.ExtraCore; +import net.kdt.pojavlaunch.extra.ExtraListener; import net.kdt.pojavlaunch.fragments.ConsoleFragment; import net.kdt.pojavlaunch.fragments.CrashFragment; import net.kdt.pojavlaunch.fragments.LauncherFragment; -import net.kdt.pojavlaunch.prefs.LauncherPreferenceFragment; import net.kdt.pojavlaunch.prefs.LauncherPreferences; +import net.kdt.pojavlaunch.prefs.screens.LauncherPreferenceFragment; import net.kdt.pojavlaunch.value.MinecraftAccount; import java.io.File; import java.util.ArrayList; import java.util.List; -import static android.os.Build.VERSION_CODES.P; -import static net.kdt.pojavlaunch.Tools.ignoreNotch; -import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_HIDE_SIDEBAR; -import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_IGNORE_NOTCH; -import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE; - public class PojavLauncherActivity extends BaseLauncherActivity { - private ViewPager viewPager; + // An equivalent ViewPager2 adapter class + private static class ScreenSlidePagerAdapter extends FragmentStateAdapter { + public ScreenSlidePagerAdapter(FragmentActivity fa) { + super(fa); + } + + @Override + public Fragment createFragment(int position) { + if (position == 0) return new LauncherFragment(); + if (position == 1) return new ConsoleFragment(); + if (position == 2) return new CrashFragment(); + if (position == 3) return new LauncherPreferenceFragment(); + return null; + } + + @Override + public int getItemCount() { + return 4; + } + } + - private TextView tvUsernameView, tvConnectStatus; + private TextView tvConnectStatus; private Spinner accountSelector; - private ViewPagerAdapter viewPageAdapter; + private ViewPager2 viewPager; private final Button[] Tabs = new Button[4]; - private View selected; + private View selectedTab; + private ImageView accountFaceImageView; private Button logoutBtn; // MineButtons + private ExtraListener backPreferenceListener; public PojavLauncherActivity() { } @Override - protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.launcher_main_v4); + //Boilerplate linking/initialisation + viewPager = findViewById(R.id.launchermainTabPager); + selectedTab = findViewById(R.id.viewTabSelected); + tvConnectStatus = findViewById(R.id.launchermain_text_accountstatus); + accountFaceImageView = findViewById(R.id.launchermain_account_image); + accountSelector = findViewById(R.id.launchermain_spinner_account); + mVersionSelector = findViewById(R.id.launchermain_spinner_version); + mLaunchProgress = findViewById(R.id.progressDownloadBar); + mLaunchTextStatus = findViewById(R.id.progressDownloadText); + logoutBtn = findViewById(R.id.installJarButton); + mPlayButton = findViewById(R.id.launchermainPlayButton); + Tabs[0] = findViewById(R.id.btnTab1); + Tabs[1] = findViewById(R.id.btnTab2); + Tabs[2] = findViewById(R.id.btnTab3); + Tabs[3] = findViewById(R.id.btnTab4); + if (BuildConfig.DEBUG) { Toast.makeText(this, "Launcher process id: " + android.os.Process.myPid(), Toast.LENGTH_LONG).show(); } - - viewPager = findViewById(R.id.launchermainTabPager); - selected = findViewById(R.id.viewTabSelected); - - mConsoleView = new ConsoleFragment(); - mCrashView = new CrashFragment(); - - viewPageAdapter = new ViewPagerAdapter(getSupportFragmentManager()); - viewPageAdapter.addFragment(new LauncherFragment(), 0, getString(R.string.mcl_tab_news)); - viewPageAdapter.addFragment(mConsoleView, 0, getString(R.string.mcl_tab_console)); - viewPageAdapter.addFragment(mCrashView, 0, getString(R.string.mcl_tab_crash)); - viewPageAdapter.addFragment(new LauncherPreferenceFragment(), 0, getString(R.string.mcl_option_settings)); - - viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + // Setup the viewPager to slide across fragments + viewPager.setAdapter(new ScreenSlidePagerAdapter(this)); + viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { @Override public void onPageSelected(int position) { setTabActive(position); } + }); + initTabs(0); + //Setup listener to the backPreference system + backPreferenceListener = new ExtraListener() { @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - - } - - @Override - public void onPageScrollStateChanged(int state) { - + public boolean onValueSet(String key, String value) { + if(value.equals("true")){ + onBackPressed(); + ExtraCore.setValue(key, "false"); + } + return false; } - }); - viewPager.setAdapter(viewPageAdapter); - - tvConnectStatus = (TextView) findViewById(R.id.launchermain_text_accountstatus); - tvUsernameView = (TextView) findViewById(R.id.launchermain_text_welcome); - mTextVersion = (TextView) findViewById(R.id.launcherMainVersionView); - - //The following line is used to make this TextView horizontally scroll if the version name is larger than the view - mTextVersion.setSelected(true); - - Tabs[0] = findViewById(R.id.btnTab1); - Tabs[1] = findViewById(R.id.btnTab2); - Tabs[2] = findViewById(R.id.btnTab3); - Tabs[3] = findViewById(R.id.btnTab4); - + }; + ExtraCore.addExtraListener("back_preference", backPreferenceListener); - pickAccount(); - - final List accountList = new ArrayList(); - final MinecraftAccount tempProfile = PojavProfile.getTempProfileContent(this); + // Try to load the temporary account + final List accountList = new ArrayList<>(); + final MinecraftAccount tempProfile = PojavProfile.getTempProfileContent(); if (tempProfile != null) { accountList.add(tempProfile.username); } for (String s : new File(Tools.DIR_ACCOUNT_NEW).list()) { accountList.add(s.substring(0, s.length() - 5)); } - + + // Setup account spinner + pickAccount(); ArrayAdapter adapterAcc = new ArrayAdapter(this, android.R.layout.simple_spinner_item, accountList); adapterAcc.setDropDownViewResource(android.R.layout.simple_list_item_single_choice); - accountSelector = (Spinner) findViewById(R.id.launchermain_spinner_account); accountSelector.setAdapter(adapterAcc); + if (tempProfile != null) { accountSelector.setSelection(0); } else { @@ -157,8 +175,9 @@ public void onNothingSelected(AdapterView p1) { // TODO: Implement this method } }); - - List versions = new ArrayList(); + + // Setup the minecraft version list + List versions = new ArrayList<>(); final File fVers = new File(Tools.DIR_HOME_VERSION); try { @@ -179,40 +198,28 @@ public void onNothingSelected(AdapterView p1) { } //mAvailableVersions; - ArrayAdapter adapterVer = new ArrayAdapter(this, android.R.layout.simple_spinner_item, mAvailableVersions); + ArrayAdapter adapterVer = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, mAvailableVersions); adapterVer.setDropDownViewResource(android.R.layout.simple_list_item_single_choice); - mVersionSelector = (Spinner) findViewById(R.id.launchermain_spinner_version); mVersionSelector.setAdapter(adapterVer); - mLaunchProgress = (ProgressBar) findViewById(R.id.progressDownloadBar); - mLaunchTextStatus = (TextView) findViewById(R.id.progressDownloadText); - logoutBtn = (Button) findViewById(R.id.switchUserBtn); - - mPlayButton = (Button) findViewById(R.id.launchermainPlayButton); - statusIsLaunching(false); - initTabs(0); - LauncherPreferences.DEFAULT_PREF.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() { - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if(key.equals("hideSidebar")){ - changeLookAndFeel(sharedPreferences.getBoolean("hideSidebar",false)); - return; - } + //Add the preference changed listener + LauncherPreferences.DEFAULT_PREF.registerOnSharedPreferenceChangeListener((sharedPreferences, key) -> { + if(key.equals("hideSidebar")){ + changeLookAndFeel(sharedPreferences.getBoolean("hideSidebar",false)); + return; + } - if(key.equals("ignoreNotch")){ - ignoreNotch(sharedPreferences.getBoolean("ignoreNotch", true), PojavLauncherActivity.this); - return; - } + if(key.equals("ignoreNotch")){ + ignoreNotch(sharedPreferences.getBoolean("ignoreNotch", true), PojavLauncherActivity.this); + return; } }); changeLookAndFeel(PREF_HIDE_SIDEBAR); - ignoreNotch(PREF_IGNORE_NOTCH, PojavLauncherActivity.this); } - private void selectTabPage(int pageIndex){ viewPager.setCurrentItem(pageIndex); setTabActive(pageIndex); @@ -221,8 +228,9 @@ private void selectTabPage(int pageIndex){ private void pickAccount() { try { mProfile = PojavProfile.getCurrentProfileContent(this); + accountFaceImageView.setImageBitmap(mProfile.getSkinFace()); - tvUsernameView.setText(getString(R.string.main_welcome, mProfile.username)); + //TODO FULL BACKGROUND LOGIN tvConnectStatus.setText(mProfile.accessToken.equals("0") ? R.string.mcl_account_offline : R.string.mcl_account_connected); } catch(Exception e) { mProfile = new MinecraftAccount(); @@ -259,21 +267,18 @@ private void setTabActive(int index){ Tabs[index].setTextColor(Color.WHITE); //Animating the white bar on the left - ValueAnimator animation = ValueAnimator.ofFloat(selected.getY(), Tabs[index].getY()+(Tabs[index].getHeight()-selected.getHeight())/2f); + ValueAnimator animation = ValueAnimator.ofFloat(selectedTab.getY(), Tabs[index].getY()+(Tabs[index].getHeight()- selectedTab.getHeight())/2f); animation.setDuration(250); - animation.addUpdateListener(animation1 -> selected.setY((float) animation1.getAnimatedValue())); + animation.addUpdateListener(animation1 -> selectedTab.setY((float) animation1.getAnimatedValue())); animation.start(); } protected void initTabs(int activeTab){ final Handler handler = new Handler(Looper.getMainLooper()); - handler.postDelayed(new Runnable() { - @Override - public void run() { - //Do something after 100ms - selectTabPage(activeTab); - } - }, 500); + handler.post(() -> { + //Do something after 100ms + selectTabPage(activeTab); + }); } private void changeLookAndFeel(boolean useOldLook){ @@ -286,8 +291,9 @@ private void changeLookAndFeel(boolean useOldLook){ params.guidePercent = 0; // 0%, range: 0 <-> 1 guideLine.setLayoutParams(params); - //Remove the selected Tab - selected.setVisibility(View.GONE); + //Remove the selected Tab and the head image + selectedTab.setVisibility(View.GONE); + accountFaceImageView.setVisibility(View.GONE); //Enlarge the button, but just a bit. params = (ConstraintLayout.LayoutParams) mPlayButton.getLayoutParams(); @@ -299,7 +305,8 @@ private void changeLookAndFeel(boolean useOldLook){ guideLine.setLayoutParams(params); //Show the selected Tab - selected.setVisibility(View.VISIBLE); + selectedTab.setVisibility(View.VISIBLE); + accountFaceImageView.setVisibility(View.VISIBLE); //Set the default button size params = (ConstraintLayout.LayoutParams) mPlayButton.getLayoutParams(); @@ -323,5 +330,22 @@ public void onAttachedToWindow() { } } + /** + * Custom back stack system. Use the classic backstack when the focus is on the setting screen, + * finish the activity and remove the back_preference listener otherwise + */ + @Override + public void onBackPressed() { + int count = getSupportFragmentManager().getBackStackEntryCount(); + + if(count > 0 && viewPager.getCurrentItem() == 3){ + getSupportFragmentManager().popBackStack(); + }else{ + super.onBackPressed(); + //additional code + ExtraCore.removeExtraListenerFromValue("back_preference", backPreferenceListener); + finish(); + } + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLoginActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLoginActivity.java index 3bd485b38b..7f71feffe2 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLoginActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLoginActivity.java @@ -1,6 +1,7 @@ package net.kdt.pojavlaunch; import static net.kdt.pojavlaunch.Architecture.archAsString; +import static net.kdt.pojavlaunch.Tools.getFileName; import android.Manifest; import android.app.Activity; @@ -379,7 +380,7 @@ protected void onActivityResult(int requestCode, int resultCode, @Nullable Inten final Uri uri = data.getData(); Thread t = new Thread(() -> { try { - MultiRTUtils.installRuntimeNamed(getContentResolver().openInputStream(uri), BaseLauncherActivity.getFileName(this, uri), + MultiRTUtils.installRuntimeNamed(getContentResolver().openInputStream(uri), getFileName(this, uri), (resid, stuff) -> PojavLoginActivity.this.runOnUiThread( () -> { if (startupTextView != null) @@ -507,14 +508,8 @@ public void loginSavedAcc(View view) { ImageView imageView = child.findViewById(R.id.account_head); String accNameStr = s.substring(0, s.length() - 5); - String skinFaceBase64 = MinecraftAccount.load(accNameStr).skinFaceBase64; - if (skinFaceBase64 != null) { - byte[] faceIconBytes = Base64.decode(skinFaceBase64, Base64.DEFAULT); - Bitmap bitmap = BitmapFactory.decodeByteArray(faceIconBytes, 0, faceIconBytes.length); + imageView.setImageBitmap(MinecraftAccount.load(accNameStr).getSkinFace()); - imageView.setImageDrawable(new BitmapDrawable(getResources(), - bitmap)); - } accountName.setText(accNameStr); accountListLayout.addView(child); @@ -635,7 +630,6 @@ public void onLoginDone(String[] result) { builder.clientToken = result[2]; builder.profileId = result[3]; builder.username = result[4]; - builder.selectedVersion = "1.12.2"; builder.updateSkinFace(); mProfile = builder; } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavProfile.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavProfile.java index 999651a781..8a6c1b339e 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavProfile.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavProfile.java @@ -25,13 +25,13 @@ public static SharedPreferences getPrefs(Context ctx) { public static MinecraftAccount getCurrentProfileContent(Context ctx) throws JsonSyntaxException { MinecraftAccount build = MinecraftAccount.load(getCurrentProfileName(ctx)); if (build == null) { - System.out.println("isTempProfile null? " + (getTempProfileContent(ctx) == null)); - return getTempProfileContent(ctx); + System.out.println("isTempProfile null? " + (getTempProfileContent() == null)); + return getTempProfileContent(); } return build; } - public static MinecraftAccount getTempProfileContent(Context ctx) { + public static MinecraftAccount getTempProfileContent() { try { MinecraftAccount acc = MinecraftAccount.parse(Tools.read(Tools.DIR_DATA+"/cache/tempacc.json")); if (acc.accessToken == null) { diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/TapDetector.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/TapDetector.java new file mode 100644 index 0000000000..9aaeeeeca6 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/TapDetector.java @@ -0,0 +1,122 @@ +package net.kdt.pojavlaunch; + +import android.view.MotionEvent; + +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_POINTER_DOWN; +import static android.view.MotionEvent.ACTION_POINTER_UP; +import static android.view.MotionEvent.ACTION_UP; + +/** + * Class aiming at better detecting X-tap events regardless of the POINTERS + * Only uses the least amount of events possible, + * since we aren't guaranteed to have all events in order + */ +public class TapDetector { + + public final static int DETECTION_METHOD_DOWN = 0x1; + public final static int DETECTION_METHOD_UP = 0x2; + public final static int DETECTION_METHOD_BOTH = 0x3; //Unused for now + + private final static int TAP_MIN_DELTA_MS = 10; + private final static int TAP_MAX_DELTA_MS = 300; + private final static int TAP_SLOP_SQUARE_PX = (int) Math.pow(Tools.dpToPx(100), 2); + + private final int tapNumberToDetect; + private int currentTapNumber = 0; + + private final int detectionMethod; + + private long mLastEventTime = 0; + private float mLastX = 9999; + private float mLastY = 9999; + + /** + * @param tapNumberToDetect How many taps are needed before onTouchEvent returns True. + * @param detectionMethod Method used to detect touches. See DETECTION_METHOD constants above. + */ + public TapDetector(int tapNumberToDetect, int detectionMethod){ + this.detectionMethod = detectionMethod; + //We expect both ACTION_DOWN and ACTION_UP for the DETECTION_METHOD_BOTH + this.tapNumberToDetect = detectBothTouch() ? 2*tapNumberToDetect : tapNumberToDetect; + } + + /** + * A function to call when you have a touch event. + * @param e The MotionEvent to inspect + * @return whether or not a X-tap happened for a pointer + */ + public boolean onTouchEvent(MotionEvent e){ + int eventAction = e.getActionMasked(); + int pointerIndex = -1; + + //Get the event to look forward + if(detectDownTouch()){ + if(eventAction == ACTION_DOWN) pointerIndex = 0; + else if(eventAction == ACTION_POINTER_DOWN) pointerIndex = e.getActionIndex(); + } + if(detectUpTouch()){ + if(eventAction == ACTION_UP) pointerIndex = 0; + else if(eventAction == ACTION_POINTER_UP) pointerIndex = e.getActionIndex(); + } + + if(pointerIndex == -1) return false; // Useless event + + //Store current event info + float eventX = e.getX(pointerIndex); + float eventY = e.getY(pointerIndex); + long eventTime = e.getEventTime(); + + //Compute deltas + long deltaTime = eventTime - mLastEventTime; + int deltaX = (int) mLastX - (int) eventX; + int deltaY = (int) mLastY - (int) eventY; + + //Store current event info to persist on next event + mLastEventTime = eventTime; + mLastX = eventX; + mLastY = eventY; + + //Check for high enough speed and precision + if(currentTapNumber > 0){ + if ((deltaTime < TAP_MIN_DELTA_MS || deltaTime > TAP_MAX_DELTA_MS) || + ((deltaX*deltaX + deltaY*deltaY) > TAP_SLOP_SQUARE_PX)) { + // We invalidate previous taps, not this one though + currentTapNumber = 0; + } + } + + //A worthy tap happened + currentTapNumber += 1; + if(currentTapNumber >= tapNumberToDetect){ + resetTapDetectionState(); + return true; + } + + //If not enough taps are reached + return false; + } + + /** + * Reset the double tap values. + */ + private void resetTapDetectionState(){ + currentTapNumber = 0; + mLastEventTime = 0; + mLastX = 9999; + mLastY = 9999; + } + + + private boolean detectDownTouch(){ + return (detectionMethod & DETECTION_METHOD_DOWN) == DETECTION_METHOD_DOWN; + } + + private boolean detectUpTouch(){ + return (detectionMethod & DETECTION_METHOD_UP) == DETECTION_METHOD_UP; + } + + private boolean detectBothTouch(){ + return detectionMethod == DETECTION_METHOD_BOTH; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java index dfa198dfb6..655bf6977a 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java @@ -2,8 +2,10 @@ import android.app.*; import android.content.*; +import android.database.Cursor; import android.net.*; import android.os.*; +import android.provider.OpenableColumns; import android.system.*; import android.util.*; import com.google.gson.*; @@ -861,4 +863,26 @@ public static int getDisplayFriendlyRes(int displaySideRes, float scaling){ if(displaySideRes % 2 != 0) displaySideRes ++; return displaySideRes; } + + public static String getFileName(Context ctx, Uri uri) { + String result = null; + if (uri.getScheme().equals("content")) { + Cursor cursor = ctx.getContentResolver().query(uri, null, null, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + } + } finally { + cursor.close(); + } + } + if (result == null) { + result = uri.getPath(); + int cut = result.lastIndexOf('/'); + if (cut != -1) { + result = result.substring(cut + 1); + } + } + return result; + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftAuthTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftAuthTask.java index ee6a6ed677..6426dac887 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftAuthTask.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftAuthTask.java @@ -71,7 +71,7 @@ public Object doInBackground(String... args) { */ Msa msa = new Msa(this, Boolean.parseBoolean(args[0]), args[1]); - MinecraftAccount acc = new MinecraftAccount(); + MinecraftAccount acc = MinecraftAccount.load(msa.mcName); if (msa.doesOwnGame) { acc.clientToken = "0"; /* FIXME */ acc.accessToken = msa.mcToken; diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlButton.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlButton.java index 22c25e977e..92ec54d92a 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlButton.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlButton.java @@ -23,6 +23,10 @@ import static net.kdt.pojavlaunch.BaseMainActivity.sendMouseButton; import static net.kdt.pojavlaunch.LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN; import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_BUTTONSIZE; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_BOTTOM_OFFSET; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_LEFT_OFFSET; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_RIGHT_OFFSET; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_TOP_OFFSET; @SuppressLint("ViewConstructor") public class ControlButton extends androidx.appcompat.widget.AppCompatButton implements OnLongClickListener @@ -152,16 +156,57 @@ protected void onVisibilityChanged(View changedView, int visibility) { @Override public void setX(float x) { + // We have to account for control offset preference + if(x + (mProperties.getWidth()/2f) > CallbackBridge.physicalWidth/2f){ + x -= PREF_CONTROL_RIGHT_OFFSET; + }else{ + x += PREF_CONTROL_LEFT_OFFSET; + } + super.setX(x); setModified(true); } @Override public void setY(float y) { + // We have to account for control offset preference + if(y - PREF_CONTROL_TOP_OFFSET + (mProperties.getHeight()/2f) > CallbackBridge.physicalHeight/2f){ + y -= PREF_CONTROL_BOTTOM_OFFSET; + }else{ + y += PREF_CONTROL_TOP_OFFSET; + } + super.setY(y); setModified(true); } + @Override + public float getX() { + float x = super.getX(); + // We have to account for control offset preference + if(x + (mProperties.getWidth()/2f) > (CallbackBridge.physicalWidth)/2f){ + x += PREF_CONTROL_RIGHT_OFFSET; + }else{ + x -= PREF_CONTROL_LEFT_OFFSET; + } + + return x; + } + + @Override + public float getY(){ + // We have to account for control offset preference + float y = super.getY(); + if(y + (mProperties.getHeight()/2f) > CallbackBridge.physicalHeight/2f){ + y += PREF_CONTROL_BOTTOM_OFFSET; + }else{ + y -= PREF_CONTROL_TOP_OFFSET; + } + + + return y; + } + /** * Apply the dynamic equation on the x axis. * @param dynamicX The equation to compute the position from diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/Gamepad.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/Gamepad.java index dfb56b4746..175e8e4f14 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/Gamepad.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/Gamepad.java @@ -3,6 +3,8 @@ import android.os.Handler; import android.os.Looper; +import android.util.Log; +import android.view.Choreographer; import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; @@ -59,10 +61,22 @@ public class Gamepad { private final boolean mModifierDigitalTriggers; private boolean mModifierSwappedAxis = true; //Triggers and right stick axis are swapped. - private final Handler inputHandler = new Handler(Looper.getMainLooper()); - private final Runnable switchStateRunnable; + private final Choreographer screenChoreographer; + private long lastFrameTime; public Gamepad(BaseMainActivity gameActivity, InputDevice inputDevice){ + screenChoreographer = Choreographer.getInstance(); + Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + updateGrabbingState(); + tick(frameTimeNanos); + screenChoreographer.postFrameCallback(this); + } + }; + screenChoreographer.postFrameCallback(frameCallback); + lastFrameTime = System.nanoTime(); + //Toast.makeText(gameActivity.getApplicationContext(),"GAMEPAD CREATED", Toast.LENGTH_LONG).show(); for(InputDevice.MotionRange range : inputDevice.getMotionRanges()){ if(range.getAxis() == MotionEvent.AXIS_RTRIGGER @@ -88,46 +102,11 @@ public Gamepad(BaseMainActivity gameActivity, InputDevice inputDevice){ pointerView.getDrawable().setFilterBitmap(false); notifyGUISizeChange(gameActivity.getMcScale()); - Runnable handlerRunnable = new Runnable() { - - @Override - public void run() { - updateGrabbingState(); - tick(); - - inputHandler.postDelayed(this, 16); - } - }; - - inputHandler.postDelayed(handlerRunnable, 16); - - //Initialize runnables to be used by the input system, avoiding generating one each time is better memory. - switchStateRunnable = () -> { - currentMap.resetPressedState(); - if(lastGrabbingState){ - currentMap = gameMap; - pointerView.setVisibility(View.INVISIBLE); - mouseSensitivity = 18; - return; - } - - currentMap = menuMap; - sendDirectionalKeycode(currentJoystickDirection, false, gameMap); // removing what we were doing - - gameActivity.mouse_x = CallbackBridge.windowWidth/2; - gameActivity.mouse_y = CallbackBridge.windowHeight/2; - CallbackBridge.sendCursorPos(gameActivity.mouse_x, gameActivity.mouse_y); - placePointerView(CallbackBridge.physicalWidth/2, CallbackBridge.physicalHeight/2); - pointerView.setVisibility(View.VISIBLE); - //sensitivity in menu is MC and HARDWARE resolution dependent - mouseSensitivity = 19 * gameActivity.scaleFactor / gameActivity.sensitivityFactor; - }; } - - private void tick(){ + public void tick(long frameTimeNanos){ //update mouse position if(lastHorizontalValue != 0 || lastVerticalValue != 0){ GamepadJoystick currentJoystick = lastGrabbingState ? leftJoystick : rightJoystick; @@ -136,8 +115,15 @@ private void tick(){ acceleration = Math.pow(acceleration, mouseMaxAcceleration); if(acceleration > 1) acceleration = 1; - CallbackBridge.mouseX += Math.cos(mouseAngle) * acceleration * mouseSensitivity; - CallbackBridge.mouseY -= Math.sin(mouseAngle) * acceleration * mouseSensitivity; + // Compute delta since last tick time + float deltaX = (float) (Math.cos(mouseAngle) * acceleration * mouseSensitivity); + float deltaY = (float) (Math.sin(mouseAngle) * acceleration * mouseSensitivity); + float deltaTimeScale = ((frameTimeNanos - lastFrameTime) / 16666666f); // Scale of 1 = 60Hz + deltaX *= deltaTimeScale; + deltaY *= deltaTimeScale; + + CallbackBridge.mouseX += deltaX; + CallbackBridge.mouseY -= deltaY; if(!lastGrabbingState){ CallbackBridge.mouseX = MathUtils.clamp(CallbackBridge.mouseX, 0, CallbackBridge.windowWidth); @@ -152,14 +138,36 @@ private void tick(){ CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY); } + // Update last nano time + lastFrameTime = frameTimeNanos; } + /** Update the grabbing state, and change the currentMap, mouse position and sensibility */ private void updateGrabbingState() { boolean lastGrabbingValue = lastGrabbingState; lastGrabbingState = CallbackBridge.isGrabbing(); - if(lastGrabbingValue != lastGrabbingState){ - gameActivity.runOnUiThread(switchStateRunnable); + if(lastGrabbingValue == lastGrabbingState) return; + + // Switch grabbing state then + currentMap.resetPressedState(); + if(lastGrabbingState){ + currentMap = gameMap; + pointerView.setVisibility(View.INVISIBLE); + mouseSensitivity = 18; + return; } + + currentMap = menuMap; + sendDirectionalKeycode(currentJoystickDirection, false, gameMap); // removing what we were doing + + gameActivity.mouse_x = CallbackBridge.windowWidth/2; + gameActivity.mouse_y = CallbackBridge.windowHeight/2; + CallbackBridge.sendCursorPos(gameActivity.mouse_x, gameActivity.mouse_y); + placePointerView(CallbackBridge.physicalWidth/2, CallbackBridge.physicalHeight/2); + pointerView.setVisibility(View.VISIBLE); + // Sensitivity in menu is MC and HARDWARE resolution dependent + mouseSensitivity = 19 * gameActivity.scaleFactor / gameActivity.sensitivityFactor; + } public void update(KeyEvent event){ @@ -188,10 +196,10 @@ private void updateDirectionalJoystick(MotionEvent event){ int lastJoystickDirection = currentJoystickDirection; currentJoystickDirection = currentJoystick.getHeightDirection(event); - if(currentJoystickDirection != lastJoystickDirection){ - sendDirectionalKeycode(lastJoystickDirection, false, getCurrentMap()); - sendDirectionalKeycode(currentJoystickDirection, true, getCurrentMap()); - } + if(currentJoystickDirection == lastJoystickDirection) return; + + sendDirectionalKeycode(lastJoystickDirection, false, getCurrentMap()); + sendDirectionalKeycode(currentJoystickDirection, true, getCurrentMap()); } private void updateAnalogTriggers(MotionEvent event){ diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlButtonPopup.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlButtonPopup.java index eb6ff7fb90..6b4071bb31 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlButtonPopup.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlButtonPopup.java @@ -29,7 +29,7 @@ public class EditControlButtonPopup { - protected Dialog dialog; + protected AlertDialog dialog; protected View v; protected AlertDialog.Builder builder; @@ -75,7 +75,6 @@ public EditControlButtonPopup(ControlButton button){ dialog = builder.create(); dialog.setOnShowListener(dialogInterface -> setEditDialogValues()); - dialog.show(); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlDrawerPopup.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlDrawerPopup.java index 593832452e..6055be5ed3 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlDrawerPopup.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlDrawerPopup.java @@ -5,6 +5,7 @@ import android.view.View; import android.widget.ArrayAdapter; import android.widget.Spinner; +import android.widget.Toast; import net.kdt.pojavlaunch.R; import net.kdt.pojavlaunch.customcontrols.ControlData; @@ -32,6 +33,11 @@ protected void hideUselessViews() { checkPassThrough.setVisibility(View.GONE); checkToggle.setVisibility(View.GONE); checkBoxSwipeable.setVisibility(View.GONE); + + (v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE); + (v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE); + editDynamicX.setVisibility(View.GONE); + editDynamicY.setVisibility(View.GONE); } @Override @@ -52,21 +58,30 @@ protected void setEditDialogValues() { super.setEditDialogValues(); spinnerOrientation.setSelection(ControlDrawerData.orientationToInt(drawerData.orientation)); - } - @Override - protected void setupDialogButtons() { - super.setupDialogButtons(); - builder.setNeutralButton(v.getResources().getString(R.string.customctrl_addsubbutton), (dialogInterface, i) -> { + //Using the dialog to replace the button behavior allows us not to dismiss the window + dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(v -> { ControlLayout layout = (ControlLayout) drawer.getParent(); ControlData controlData = new ControlData(drawerData.properties); controlData.name = "new"; layout.addSubButton(drawer, controlData); + + Context ctx = dialog.getContext(); + Toast.makeText(ctx, ctx.getString(R.string.customctrl_add_subbutton_message, + drawer.getDrawerData().buttonProperties.size()), Toast.LENGTH_SHORT).show(); }); } + @Override + protected void setupDialogButtons() { + super.setupDialogButtons(); + + builder.setNeutralButton(v.getResources().getString(R.string.customctrl_addsubbutton), null); + + } + @Override protected void saveProperties() { drawerData.orientation = ControlDrawerData.intToOrientation(spinnerOrientation.getSelectedItemPosition()); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraCore.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraCore.java new file mode 100644 index 0000000000..eabb85aa16 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraCore.java @@ -0,0 +1,129 @@ +package net.kdt.pojavlaunch.extra; + +import java.lang.ref.WeakReference; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * Class providing callback across all of a program + * to allow easy thread safe implementations of UI update without context leak + * + * This class uses a singleton pattern to simplify access to it + */ +public final class ExtraCore { + // No unwanted instantiation + private ExtraCore(){} + + // Store the key-value pair + private final Map valueMap = new ConcurrentHashMap<>(); + + // Store what each ExtraListener listen to + private final Map>> listenerMap = new ConcurrentHashMap<>(); + + // Inner class for singleton implementation + private static class ExtraCoreSingleton { + private static final ExtraCore extraCore = new ExtraCore(); + } + + // All public methods will pass through this one + private static ExtraCore getInstance(){ + return ExtraCoreSingleton.extraCore; + } + + /** + * Set the value associated to a key and trigger all listeners + * @param key The key + * @param value The value + */ + public static void setValue(String key, String value){ + getInstance().valueMap.put(key, value); + ConcurrentLinkedQueue> extraListenerList = getInstance().listenerMap.get(key); + for(WeakReference listener : extraListenerList){ + if(listener.get() == null){ + extraListenerList.remove(listener); + continue; + } + listener.get().notifyDataChanged(key, value); + } + } + + /** @return The value behind the key */ + public static String getValue(String key){ + return getInstance().valueMap.get(key); + } + + /** Remove the key and its value from the valueMap */ + public static void removeValue(String key){ + getInstance().valueMap.remove(key); + } + + /** Remove all values */ + public static void removeAllValues(){ + getInstance().valueMap.clear(); + } + + /** + * Link an ExtraListener to a value + * @param key The value key to look for + * @param listener The ExtraListener to link + */ + public static void addExtraListener(String key, ExtraListener listener){ + ConcurrentLinkedQueue> listenerList = getInstance().listenerMap.get(key); + // Look for new sets + if(listenerList == null){ + listenerList = new ConcurrentLinkedQueue<>(); + getInstance().listenerMap.put(key, listenerList); + } + + // This is kinda naive, I should look for duplicates + listenerList.add(new WeakReference<>(listener)); + } + + /** + * Unlink an ExtraListener from a value. + * Unlink null references found along the way + * @param key The value key to ignore now + * @param listener The ExtraListener to unlink + */ + public static void removeExtraListenerFromValue(String key, ExtraListener listener){ + ConcurrentLinkedQueue> listenerList = getInstance().listenerMap.get(key); + // Look for new sets + if(listenerList == null){ + listenerList = new ConcurrentLinkedQueue<>(); + getInstance().listenerMap.put(key, listenerList); + } + + // Removes all occurrences of ExtraListener and all null references + for(WeakReference listenerWeakReference : listenerList){ + ExtraListener actualListener = listenerWeakReference.get(); + + if(actualListener == null || actualListener == listener){ + listenerList.remove(listenerWeakReference); + } + } + } + + /** + * Unlink all ExtraListeners from a value + * @param key The key to which ExtraListener are linked + */ + public static void removeAllExtraListenersFromValue(String key){ + ConcurrentLinkedQueue> listenerList = getInstance().listenerMap.get(key); + // Look for new sets + if(listenerList == null){ + listenerList = new ConcurrentLinkedQueue<>(); + getInstance().listenerMap.put(key, listenerList); + } + + listenerList.clear(); + } + + /** + * Remove all ExtraListeners from listening to any value + */ + public static void removeAllExtraListeners(){ + getInstance().listenerMap.clear(); + } + +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraListener.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraListener.java new file mode 100644 index 0000000000..4c310bdae4 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraListener.java @@ -0,0 +1,26 @@ +package net.kdt.pojavlaunch.extra; + +/** + * Listener class for the ExtraCore + * An ExtraListener can listen to a virtually unlimited amount of values + */ +public abstract class ExtraListener { + + /** + * Called by the ExtraCore after a value is set. + * Technically, it can be triggered from outside but is seems pointless + */ + public final void notifyDataChanged(String key, String value){ + if(onValueSet(key, value)){ + ExtraCore.removeExtraListenerFromValue(key, this); + } + } + + /** + * Called upon a new value being set + * @param key The name of the value + * @param value The new value as a string + * @return Whether you consume the Listener (stop listening) + */ + public abstract boolean onValueSet(String key, String value); +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/BackButtonPreference.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/BackButtonPreference.java new file mode 100644 index 0000000000..8621cbe2ff --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/BackButtonPreference.java @@ -0,0 +1,36 @@ +package net.kdt.pojavlaunch.prefs; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.preference.Preference; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.extra.ExtraCore; + +public class BackButtonPreference extends Preference { + public BackButtonPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public BackButtonPreference(Context context) { + this(context, null); + } + + private void init(){ + if(getTitle() == null){ + setTitle(R.string.preference_back_title); + } + if(getIcon() == null){ + setIcon(R.drawable.ic_arrow_back_white); + } + } + + + @Override + protected void onClick() { + // It is caught by an ExtraListener in the LauncherActivity + ExtraCore.setValue("back_preference", "true"); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/ControlOffsetPreference.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/ControlOffsetPreference.java new file mode 100644 index 0000000000..c11c569eb0 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/ControlOffsetPreference.java @@ -0,0 +1,127 @@ +package net.kdt.pojavlaunch.prefs; + +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.DEFAULT_PREF; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_BOTTOM_OFFSET; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_LEFT_OFFSET; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_RIGHT_OFFSET; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_TOP_OFFSET; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.util.AttributeSet; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.preference.Preference; + +import net.kdt.pojavlaunch.R; + +/** Custom preference class displaying a dialog */ +public class ControlOffsetPreference extends Preference { + + private AlertDialog preferenceDialog; + + + + public ControlOffsetPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ControlOffsetPreference(Context context) { + super(context); + init(); + } + + + private void init(){ + // Setup visual values + if(getTitle() == null){ + setTitle(R.string.preference_control_offset_title); + setSummary(R.string.preference_control_offset_description); + } + if(getIcon() == null){ + setIcon(android.R.drawable.radiobutton_off_background); + } + + // Prepare Alert dialog + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext()); + dialogBuilder.setView(R.layout.control_offset_preference_dialog); + dialogBuilder.setTitle(getContext().getString(R.string.control_offset_title)); + + dialogBuilder.setPositiveButton(android.R.string.ok, null); + dialogBuilder.setNegativeButton(android.R.string.cancel, null); + + preferenceDialog = dialogBuilder.create(); + + + } + + @Override + protected void onClick() { + preferenceDialog.show(); + + SeekBar topOffsetSeekbar = preferenceDialog.findViewById(R.id.control_offset_top_seekbar); + SeekBar rightOffsetSeekbar = preferenceDialog.findViewById(R.id.control_offset_right_seekbar); + SeekBar bottomOffsetSeekbar = preferenceDialog.findViewById(R.id.control_offset_bottom_seekbar); + SeekBar leftOffsetSeekbar = preferenceDialog.findViewById(R.id.control_offset_left_seekbar); + + TextView topOffsetTextView = preferenceDialog.findViewById(R.id.control_offset_top_textview); + TextView rightOffsetTextView = preferenceDialog.findViewById(R.id.control_offset_right_textview); + TextView bottomOffsetTextView = preferenceDialog.findViewById(R.id.control_offset_bottom_textview); + TextView leftOffsetTextView = preferenceDialog.findViewById(R.id.control_offset_left_textview); + + SeekBar.OnSeekBarChangeListener seekBarChangeListener = new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + if(seekBar == topOffsetSeekbar){ + String text = String.format("%s%d", getContext().getString(R.string.control_top_offset), i); + topOffsetTextView.setText(text); + return; + } + if(seekBar == rightOffsetSeekbar){ + String text = String.format("%s%d", getContext().getString(R.string.control_right_offset), i); + rightOffsetTextView.setText(text); + return; + } + if(seekBar == bottomOffsetSeekbar){ + String text = String.format("%s%d", getContext().getString(R.string.control_bottom_offset), i); + bottomOffsetTextView.setText(text); + return; + } + if(seekBar == leftOffsetSeekbar){ + String text = String.format("%s%d", getContext().getString(R.string.control_left_offset), i); + leftOffsetTextView.setText(text); + return; + } + } + @Override + public void onStartTrackingTouch(SeekBar seekBar) {} + @Override + public void onStopTrackingTouch(SeekBar seekBar) {} + }; + + topOffsetSeekbar.setOnSeekBarChangeListener(seekBarChangeListener); + rightOffsetSeekbar.setOnSeekBarChangeListener(seekBarChangeListener); + bottomOffsetSeekbar.setOnSeekBarChangeListener(seekBarChangeListener); + leftOffsetSeekbar.setOnSeekBarChangeListener(seekBarChangeListener); + + topOffsetSeekbar.setProgress(PREF_CONTROL_TOP_OFFSET); + rightOffsetSeekbar.setProgress(PREF_CONTROL_RIGHT_OFFSET); + bottomOffsetSeekbar.setProgress(PREF_CONTROL_BOTTOM_OFFSET); + leftOffsetSeekbar.setProgress(PREF_CONTROL_LEFT_OFFSET); + + // Custom writing to preferences + preferenceDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(view -> { + DEFAULT_PREF.edit().putInt("controlTopOffset", topOffsetSeekbar.getProgress()).apply(); + DEFAULT_PREF.edit().putInt("controlRightOffset", rightOffsetSeekbar.getProgress()).apply(); + DEFAULT_PREF.edit().putInt("controlBottomOffset", bottomOffsetSeekbar.getProgress()).apply(); + DEFAULT_PREF.edit().putInt("controlLeftOffset", leftOffsetSeekbar.getProgress()).apply(); + + + preferenceDialog.dismiss(); + }); + } + +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/CustomSeekBarPreference.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/CustomSeekBarPreference.java index f973c8813a..f56cbe1c89 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/CustomSeekBarPreference.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/CustomSeekBarPreference.java @@ -15,10 +15,14 @@ public class CustomSeekBarPreference extends SeekBarPreference { + /** The suffix displayed */ private String suffix = ""; + /** Custom minimum value to provide the same behavior as the usual setMin */ private int mMin; + /** The textview associated by default to the preference */ private TextView textView; + public CustomSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); TypedArray a = context.obtainStyledAttributes( @@ -59,22 +63,26 @@ public void onBindViewHolder(PreferenceViewHolder view) { SeekBar seekBar = (SeekBar) view.findViewById(R.id.seekbar); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + progress = progress / getSeekBarIncrement(); + progress = progress * getSeekBarIncrement(); + textView.setText(String.valueOf(progress + mMin)); updateTextViewWithSuffix(); } @Override - public void onStartTrackingTouch(SeekBar seekBar) { - - } + public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onStopTrackingTouch(SeekBar seekBar) { - setValue(seekBar.getProgress() + mMin); - updateTextViewWithSuffix(); + int progress = seekBar.getProgress() / getSeekBarIncrement(); + progress *= getSeekBarIncrement(); + setValue(progress + mMin); + updateTextViewWithSuffix(); } }); @@ -82,7 +90,6 @@ public void onStopTrackingTouch(SeekBar seekBar) { } - private void updateTextViewWithSuffix(){ if(!textView.getText().toString().endsWith(suffix)){ textView.setText(String.format("%s%s", textView.getText(), suffix)); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferenceFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferenceFragment.java deleted file mode 100644 index fca26f1c91..0000000000 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferenceFragment.java +++ /dev/null @@ -1,104 +0,0 @@ -package net.kdt.pojavlaunch.prefs; - - -import android.graphics.Color; -import android.os.*; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.preference.*; -import net.kdt.pojavlaunch.R; -import net.kdt.pojavlaunch.fragments.LauncherFragment; - -import android.content.*; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import static net.kdt.pojavlaunch.Architecture.is32BitsDevice; -import static net.kdt.pojavlaunch.Tools.getTotalDeviceMemory; -import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE; - -public class LauncherPreferenceFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener -{ - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - view.setBackgroundColor(Color.parseColor("#44000000")); - super.onViewCreated(view, savedInstanceState); - } - - @Override - public void onCreatePreferences(Bundle b, String str) { - addPreferencesFromResource(R.xml.pref_main); - - //Disable notch checking behavior on android 8.1 and below. - findPreference("ignoreNotch").setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && PREF_NOTCH_SIZE != 0); - - CustomSeekBarPreference seek2 = findPreference("timeLongPressTrigger"); - seek2.setRange(100, 1000); - seek2.setValue(LauncherPreferences.PREF_LONGPRESS_TRIGGER); - seek2.setSuffix(" ms"); - - CustomSeekBarPreference seek3 = findPreference("buttonscale"); - seek3.setRange(80, 250); - seek3.setValue((int) LauncherPreferences.PREF_BUTTONSIZE); - seek3.setSuffix(" %"); - - CustomSeekBarPreference seek4 = findPreference("mousescale"); - seek4.setRange(25, 300); - seek4.setValue((int) LauncherPreferences.PREF_MOUSESCALE); - seek4.setSuffix(" %"); - - CustomSeekBarPreference seek5 = findPreference("resolutionRatio"); - seek5.setMin(25); - seek5.setSuffix(" %"); - - CustomSeekBarPreference seek6 = findPreference("mousespeed"); - seek6.setRange(25, 300); - seek6.setValue((int)(LauncherPreferences.PREF_MOUSESPEED*100f)); - seek6.setSuffix(" %"); - - - int maxRAM; - int deviceRam = getTotalDeviceMemory(getContext()); - - - CustomSeekBarPreference seek7 = findPreference("allocation"); - seek7.setMin(256); - - if(is32BitsDevice()) maxRAM = Math.min(1100, deviceRam); - else maxRAM = deviceRam - (deviceRam < 3064 ? 800 : 1024); //To have a minimum for the device to breathe - - seek7.setMax(maxRAM); - seek7.setValue(LauncherPreferences.PREF_RAM_ALLOCATION); - seek7.setSuffix(" MB"); - - // #724 bug fix - if (seek5.getValue() < 25) { - seek5.setValue(100); - } - - EditTextPreference editJVMArgs = findPreference("javaArgs"); - if (editJVMArgs != null) { - editJVMArgs.setOnBindEditTextListener((editText) -> editText.setSingleLine()); - } - } - - @Override - public void onResume() { - super.onResume(); - getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); - } - - @Override - public void onPause() { - getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); - super.onPause(); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences p, String s) { - LauncherPreferences.loadPreferences(getContext()); - } -} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java index ee8261885b..501edadb20 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java @@ -29,6 +29,12 @@ public class LauncherPreferences public static float PREF_MOUSESPEED = 1f; public static int PREF_RAM_ALLOCATION; public static String PREF_DEFAULT_RUNTIME; + public static int PREF_CONTROL_TOP_OFFSET = 0; + public static int PREF_CONTROL_RIGHT_OFFSET = 0; + public static int PREF_CONTROL_BOTTOM_OFFSET = 0; + public static int PREF_CONTROL_LEFT_OFFSET = 0; + + public static void loadPreferences(Context ctx) { //Required for the data folder. Tools.initContextConstants(ctx); @@ -51,6 +57,11 @@ public static void loadPreferences(Context ctx) { PREF_DISABLE_GESTURES = DEFAULT_PREF.getBoolean("disableGestures",false); PREF_RAM_ALLOCATION = DEFAULT_PREF.getInt("allocation", findBestRAMAllocation(ctx)); PREF_CUSTOM_JAVA_ARGS = DEFAULT_PREF.getString("javaArgs", ""); + PREF_CONTROL_TOP_OFFSET = DEFAULT_PREF.getInt("controlTopOffset", 0); + PREF_CONTROL_RIGHT_OFFSET = DEFAULT_PREF.getInt("controlRightOffset", 0); + PREF_CONTROL_BOTTOM_OFFSET = DEFAULT_PREF.getInt("controlBottomOffset", 0); + PREF_CONTROL_LEFT_OFFSET = DEFAULT_PREF.getInt("controlTopOffset", 0); + /* if (PREF_CUSTOM_JAVA_ARGS.isEmpty()) { String DEFAULT_JAVA_ARGS = ""; diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java new file mode 100644 index 0000000000..7c3920a26f --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java @@ -0,0 +1,58 @@ +package net.kdt.pojavlaunch.prefs.screens; + +import android.content.SharedPreferences; +import android.os.Bundle; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.prefs.CustomSeekBarPreference; +import net.kdt.pojavlaunch.prefs.LauncherPreferences; + +public class LauncherPreferenceControlFragment extends LauncherPreferenceFragment { + + @Override + public void onCreatePreferences(Bundle b, String str) { + // Get values + int longPressTrigger = LauncherPreferences.PREF_LONGPRESS_TRIGGER; + int prefButtonSize = (int) LauncherPreferences.PREF_BUTTONSIZE; + int mouseScale = (int) LauncherPreferences.PREF_MOUSESCALE; + float mouseSpeed = LauncherPreferences.PREF_MOUSESPEED; + + //Triggers a write for some reason which resets the value + addPreferencesFromResource(R.xml.pref_control); + + CustomSeekBarPreference seek2 = findPreference("timeLongPressTrigger"); + seek2.setRange(100, 1000); + seek2.setValue(longPressTrigger); + seek2.setSuffix(" ms"); + + CustomSeekBarPreference seek3 = findPreference("buttonscale"); + seek3.setRange(80, 250); + seek3.setValue(prefButtonSize); + seek3.setSuffix(" %"); + + CustomSeekBarPreference seek4 = findPreference("mousescale"); + seek4.setRange(25, 300); + seek4.setValue(mouseScale); + seek4.setSuffix(" %"); + + CustomSeekBarPreference seek6 = findPreference("mousespeed"); + seek6.setRange(25, 300); + seek6.setValue((int)(mouseSpeed *100f)); + seek6.setSuffix(" %"); + + + computeVisibility(); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences p, String s) { + super.onSharedPreferenceChanged(p, s); + computeVisibility(); + } + + private void computeVisibility(){ + CustomSeekBarPreference seek2 = findPreference("timeLongPressTrigger"); + seek2.setVisible(!LauncherPreferences.PREF_DISABLE_GESTURES); + } + +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceExperimentalFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceExperimentalFragment.java new file mode 100644 index 0000000000..6b5ea17b87 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceExperimentalFragment.java @@ -0,0 +1,13 @@ +package net.kdt.pojavlaunch.prefs.screens; + +import android.os.Bundle; + +import net.kdt.pojavlaunch.R; + +public class LauncherPreferenceExperimentalFragment extends LauncherPreferenceFragment { + + @Override + public void onCreatePreferences(Bundle b, String str) { + addPreferencesFromResource(R.xml.pref_experimental); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceFragment.java new file mode 100644 index 0000000000..531d0d30de --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceFragment.java @@ -0,0 +1,57 @@ +package net.kdt.pojavlaunch.prefs.screens; + + +import android.graphics.Color; +import android.os.*; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.*; +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.prefs.LauncherPreferences; + +import android.content.*; +import android.view.View; +import android.widget.Toast; + +import static net.kdt.pojavlaunch.Architecture.is32BitsDevice; +import static net.kdt.pojavlaunch.Tools.getTotalDeviceMemory; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE; + +/** + * Preference for the main screen, any sub-screen should inherit this class for consistent behavior, + * overriding only onCreatePreferences + */ +public class LauncherPreferenceFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + view.setBackgroundColor(Color.parseColor("#232323")); + super.onViewCreated(view, savedInstanceState); + } + + @Override + public void onCreatePreferences(Bundle b, String str) { + addPreferencesFromResource(R.xml.pref_main); + } + + @Override + public void onResume() { + super.onResume(); + getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onPause() { + getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + super.onPause(); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences p, String s) { + LauncherPreferences.loadPreferences(getContext()); + } + + +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceJavaFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceJavaFragment.java new file mode 100644 index 0000000000..9b8a41d347 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceJavaFragment.java @@ -0,0 +1,43 @@ +package net.kdt.pojavlaunch.prefs.screens; + +import static net.kdt.pojavlaunch.Architecture.is32BitsDevice; +import static net.kdt.pojavlaunch.Tools.getTotalDeviceMemory; + +import android.os.Bundle; +import android.widget.TextView; + +import androidx.preference.EditTextPreference; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.prefs.CustomSeekBarPreference; +import net.kdt.pojavlaunch.prefs.LauncherPreferences; + +public class LauncherPreferenceJavaFragment extends LauncherPreferenceFragment { + @Override + public void onCreatePreferences(Bundle b, String str) { + int ramAllocation = LauncherPreferences.PREF_RAM_ALLOCATION; + + // Triggers a write for some reason + addPreferencesFromResource(R.xml.pref_java); + + int maxRAM; + int deviceRam = getTotalDeviceMemory(getContext()); + + CustomSeekBarPreference seek7 = findPreference("allocation"); + seek7.setMin(256); + + if(is32BitsDevice()) maxRAM = Math.min(1100, deviceRam); + else maxRAM = deviceRam - (deviceRam < 3064 ? 800 : 1024); //To have a minimum for the device to breathe + + seek7.setMax(maxRAM); + seek7.setValue(ramAllocation); + seek7.setSuffix(" MB"); + + + EditTextPreference editJVMArgs = findPreference("javaArgs"); + if (editJVMArgs != null) { + editJVMArgs.setOnBindEditTextListener(TextView::setSingleLine); + } + + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceMiscellaneousFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceMiscellaneousFragment.java new file mode 100644 index 0000000000..ebb665b9aa --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceMiscellaneousFragment.java @@ -0,0 +1,12 @@ +package net.kdt.pojavlaunch.prefs.screens; + +import android.os.Bundle; + +import net.kdt.pojavlaunch.R; + +public class LauncherPreferenceMiscellaneousFragment extends LauncherPreferenceFragment { + @Override + public void onCreatePreferences(Bundle b, String str) { + addPreferencesFromResource(R.xml.pref_misc); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceVideoFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceVideoFragment.java new file mode 100644 index 0000000000..ebcbc14a2c --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceVideoFragment.java @@ -0,0 +1,33 @@ +package net.kdt.pojavlaunch.prefs.screens; + +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE; + +import android.os.Build; +import android.os.Bundle; + +import androidx.preference.PreferenceFragmentCompat; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.prefs.CustomSeekBarPreference; + +/** + * Fragment for any settings video related + */ +public class LauncherPreferenceVideoFragment extends LauncherPreferenceFragment { + @Override + public void onCreatePreferences(Bundle b, String str) { + addPreferencesFromResource(R.xml.pref_video); + + //Disable notch checking behavior on android 8.1 and below. + findPreference("ignoreNotch").setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && PREF_NOTCH_SIZE != 0); + + CustomSeekBarPreference seek5 = findPreference("resolutionRatio"); + seek5.setMin(25); + seek5.setSuffix(" %"); + + // #724 bug fix + if (seek5.getValue() < 25) { + seek5.setValue(100); + } + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/MinecraftDownloaderTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/MinecraftDownloaderTask.java index 98b24549e5..0c001a0d8a 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/MinecraftDownloaderTask.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/MinecraftDownloaderTask.java @@ -324,7 +324,7 @@ protected void onProgressUpdate(String... p1) } if (p1.length < 3) { - mActivity.mConsoleView.putLog(p1[1] + "\n"); + //mActivity.mConsoleView.putLog(p1[1] + "\n"); } } @@ -341,7 +341,7 @@ protected void onPostExecute(Throwable p1) Tools.showError(mActivity, p1); } if(!launchWithError) { - mActivity.mCrashView.setLastCrash(""); + //mActivity.mCrashView.setLastCrash(""); try { Intent mainIntent = new Intent(mActivity, MainActivity.class /* MainActivity.class */); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/RefreshVersionListTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/RefreshVersionListTask.java index 11169c39b6..d1e68d4561 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/RefreshVersionListTask.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/RefreshVersionListTask.java @@ -77,8 +77,7 @@ protected void onPostExecute(ArrayList result) @Override public void onItemSelected(AdapterView p1, View p2, int p3, long p4) { - String version = p1.getItemAtPosition(p3).toString(); - mActivity.mProfile.selectedVersion = version; + mActivity.mProfile.selectedVersion = p1.getItemAtPosition(p3).toString(); PojavProfile.setCurrentProfile(mActivity, mActivity.mProfile); if (PojavProfile.isFileType(mActivity)) { @@ -89,7 +88,6 @@ public void onItemSelected(AdapterView p1, View p2, int p3, long p4) } } - mActivity.mTextVersion.setText(mActivity.getString(R.string.mcl_version_msg, version)); } @Override @@ -109,13 +107,8 @@ public boolean onItemLongClick(AdapterView p1, View p2, int p3, long p4) } }); */ - popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - return true; - } - }); + popup.setOnMenuItemClickListener(item -> true); - mActivity.mTextVersion.setText(mActivity.getString(R.string.mcl_version_msg,mActivity.mVersionSelector.getSelectedItem())); } private ArrayList filter(JMinecraftVersionList.Version[] list1, File[] list2) { @@ -145,9 +138,8 @@ private int selectAt(String[] strArr, String select) { for(String str : strArr){ if (str.equals(select)) { return count; - } else { - count++; } + count++; } return -1; } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/MinecraftAccount.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/MinecraftAccount.java index 14bbac7808..0ff48ea3e1 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/MinecraftAccount.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/MinecraftAccount.java @@ -1,4 +1,7 @@ package net.kdt.pojavlaunch.value; + + +import android.graphics.BitmapFactory; import android.util.Log; import net.kdt.pojavlaunch.*; @@ -49,8 +52,9 @@ public String save() throws IOException { public static MinecraftAccount parse(String content) throws JsonSyntaxException { return Tools.GLOBAL_GSON.fromJson(content, MinecraftAccount.class); } - + public static MinecraftAccount load(String name) throws JsonSyntaxException { + if(!accountExists(name)) return new MinecraftAccount(); try { MinecraftAccount acc = parse(Tools.read(Tools.DIR_ACCOUNT_NEW + "/" + name + ".json")); if (acc.accessToken == null) { @@ -80,7 +84,19 @@ public static MinecraftAccount load(String name) throws JsonSyntaxException { return null; } } - + + public Bitmap getSkinFace(){ + if(skinFaceBase64 == null){ + return Bitmap.createBitmap(1,1, Bitmap.Config.ARGB_8888); + } + byte[] faceIconBytes = Base64.decode(skinFaceBase64, Base64.DEFAULT); + return BitmapFactory.decodeByteArray(faceIconBytes, 0, faceIconBytes.length); + } + + private static boolean accountExists(String username){ + return new File(Tools.DIR_ACCOUNT_NEW + "/" + username + ".json").exists(); + } + public static void clearTempAccount() { File tempAccFile = new File(Tools.DIR_DATA, "cache/tempacc.json"); tempAccFile.delete(); diff --git a/app_pojavlauncher/src/main/java/org/lwjgl/glfw/CallbackBridge.java b/app_pojavlauncher/src/main/java/org/lwjgl/glfw/CallbackBridge.java index 7582e74689..e05f12376b 100644 --- a/app_pojavlauncher/src/main/java/org/lwjgl/glfw/CallbackBridge.java +++ b/app_pojavlauncher/src/main/java/org/lwjgl/glfw/CallbackBridge.java @@ -15,7 +15,7 @@ public class CallbackBridge { public static volatile int windowWidth, windowHeight; public static volatile int physicalWidth, physicalHeight; - public static int mouseX, mouseY; + public static float mouseX, mouseY; public static StringBuilder DEBUG_STRING = new StringBuilder(); // volatile private static boolean isGrabbing = false; @@ -29,7 +29,7 @@ public PusherRunnable(int button, int x, int y) { @Override public void run() { putMouseEventWithCoords(button, true, x, y); - try { Thread.sleep(40); } catch (InterruptedException e) {} + //try { Thread.sleep(1); } catch (InterruptedException e) {} putMouseEventWithCoords(button, false, x, y); } } @@ -37,7 +37,7 @@ public static void putMouseEventWithCoords(int button, int x, int y /* , int dz, new Thread(new PusherRunnable(button,x,y)).run(); } - public static void putMouseEventWithCoords(int button, boolean isDown, int x, int y /* , int dz, long nanos */) { + public static void putMouseEventWithCoords(int button, boolean isDown, float x, float y /* , int dz, long nanos */) { sendCursorPos(x, y); sendMouseKeycode(button, CallbackBridge.getCurrentMods(), isDown); } @@ -49,8 +49,8 @@ public static void sendCursorPos(float x, float y) { } DEBUG_STRING.append("CursorPos=").append(x).append(", ").append(y).append("\n"); - mouseX = (int) x; - mouseY = (int) y; + mouseX = x; + mouseY = y; nativeSendCursorPos(mouseX, mouseY); } @@ -126,13 +126,6 @@ public static String accessAndroidClipboard(int type, String copy) { default: return null; } } - public static void receiveCallback(int type, String data) { - switch (type) { - case ANDROID_TYPE_GRAB_STATE: - // isGrabbing = Boolean.parseBoolean(data); - break; - } - } /* private static String currData; public static void sendData(int type, Object... dataArr) { @@ -201,7 +194,7 @@ public static void setModifiers(int keyCode, boolean isDown){ private static native boolean nativeSendCharMods(char codepoint, int mods); private static native void nativeSendKey(int key, int scancode, int action, int mods); // private static native void nativeSendCursorEnter(int entered); - private static native void nativeSendCursorPos(int x, int y); + private static native void nativeSendCursorPos(float x, float y); private static native void nativeSendMouseButton(int button, int action, int mods); private static native void nativeSendScroll(double xoffset, double yoffset); private static native void nativeSendScreenSize(int width, int height); diff --git a/app_pojavlauncher/src/main/jni/input_bridge_v3.c b/app_pojavlauncher/src/main/jni/input_bridge_v3.c index 4c53c50553..b9df89e760 100644 --- a/app_pojavlauncher/src/main/jni/input_bridge_v3.c +++ b/app_pojavlauncher/src/main/jni/input_bridge_v3.c @@ -37,7 +37,7 @@ typedef void GLFW_invoke_MouseButton_func(void* window, int button, int action, typedef void GLFW_invoke_Scroll_func(void* window, double xoffset, double yoffset); typedef void GLFW_invoke_WindowSize_func(void* window, int width, int height); -static int grabCursorX, grabCursorY, lastCursorX, lastCursorY; +static float grabCursorX, grabCursorY, lastCursorX, lastCursorY; jclass inputBridgeClass_ANDROID, inputBridgeClass_JRE; jmethodID inputBridgeMethod_ANDROID, inputBridgeMethod_JRE; @@ -255,7 +255,7 @@ JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendCursorEnter( } } */ -JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendCursorPos(JNIEnv* env, jclass clazz, jint x, jint y) { +JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendCursorPos(JNIEnv* env, jclass clazz, jfloat x, jfloat y) { if (GLFW_invoke_CursorPos && isInputReady) { if (!isCursorEntered) { if (GLFW_invoke_CursorEnter) { diff --git a/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libgl4es_115.so b/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libgl4es_115.so index c065a1ad59..3221f34800 100644 Binary files a/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libgl4es_115.so and b/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libgl4es_115.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libvgpu.so b/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libvgpu.so index 6e0a534f55..245c9ef682 100644 Binary files a/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libvgpu.so and b/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libvgpu.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libgl4es_115.so b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libgl4es_115.so index b313b2bc32..89684425b8 100644 Binary files a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libgl4es_115.so and b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libgl4es_115.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libvgpu.so b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libvgpu.so index cdbebbc87a..41b99ee8f2 100644 Binary files a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libvgpu.so and b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libvgpu.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86/libgl4es_115.so b/app_pojavlauncher/src/main/jniLibs/x86/libgl4es_115.so index d8c84bb069..fa42e5ab7e 100644 Binary files a/app_pojavlauncher/src/main/jniLibs/x86/libgl4es_115.so and b/app_pojavlauncher/src/main/jniLibs/x86/libgl4es_115.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86/libvgpu.so b/app_pojavlauncher/src/main/jniLibs/x86/libvgpu.so index 6d792a02d1..d70d0ca7d2 100644 Binary files a/app_pojavlauncher/src/main/jniLibs/x86/libvgpu.so and b/app_pojavlauncher/src/main/jniLibs/x86/libvgpu.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86_64/libgl4es_115.so b/app_pojavlauncher/src/main/jniLibs/x86_64/libgl4es_115.so index cd00e98537..fd59979150 100644 Binary files a/app_pojavlauncher/src/main/jniLibs/x86_64/libgl4es_115.so and b/app_pojavlauncher/src/main/jniLibs/x86_64/libgl4es_115.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86_64/libvgpu.so b/app_pojavlauncher/src/main/jniLibs/x86_64/libvgpu.so index ef52f70c90..afa4b2ca87 100644 Binary files a/app_pojavlauncher/src/main/jniLibs/x86_64/libvgpu.so and b/app_pojavlauncher/src/main/jniLibs/x86_64/libvgpu.so differ diff --git a/app_pojavlauncher/src/main/res/drawable/ic_arrow_back_white.xml b/app_pojavlauncher/src/main/res/drawable/ic_arrow_back_white.xml new file mode 100644 index 0000000000..9b1f144be3 --- /dev/null +++ b/app_pojavlauncher/src/main/res/drawable/ic_arrow_back_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app_pojavlauncher/src/main/res/layout/control_offset_preference_dialog.xml b/app_pojavlauncher/src/main/res/layout/control_offset_preference_dialog.xml new file mode 100644 index 0000000000..3475e309cf --- /dev/null +++ b/app_pojavlauncher/src/main/res/layout/control_offset_preference_dialog.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app_pojavlauncher/src/main/res/layout/launcher_main_v4.xml b/app_pojavlauncher/src/main/res/layout/launcher_main_v4.xml index 9a2761a290..52124ed32b 100644 --- a/app_pojavlauncher/src/main/res/layout/launcher_main_v4.xml +++ b/app_pojavlauncher/src/main/res/layout/launcher_main_v4.xml @@ -33,17 +33,33 @@ android:orientation="vertical" app:layout_constraintGuide_percent="0.86" /> + + + app:layout_constraintTop_toTopOf="@+id/launchermain_account_image" /> - - - - - + app:layout_constraintWidth_default="percent" + app:layout_constraintWidth_percent="0.25" />