- * 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" />
-
- - @string/mcl_option_modinstall
- - @string/mcl_option_modinstallwitharg
- - @string/mcl_option_customcontrol
- - @string/mcl_option_about
-
-
- @string/mcl_setting_renderer_gles2_4
- @string/mcl_setting_renderer_gles2_5
diff --git a/app_pojavlauncher/src/main/res/values/strings.xml b/app_pojavlauncher/src/main/res/values/strings.xml
index b84f3f14d7..f6ca458446 100644
--- a/app_pojavlauncher/src/main/res/values/strings.xml
+++ b/app_pojavlauncher/src/main/res/values/strings.xml
@@ -202,9 +202,12 @@
Add button
Add button drawer
Add sub-button
-
+ Sub-button n°%d has been added !
+
Select default Control json
-
+
+
+ Install .jar
Options
Play
Welcome, %s
@@ -252,4 +255,26 @@
Per-version settings
Storage changes
The new folder for Pojav files is Android/data/%s/files/ (also accessible using a storage provider in your file explorer). If you need to copy your game files, please copy everything from the original folder at games/PojavLauncher manually.]]>
+
+ Control offset
+ Top offset:
+ right offset:
+ bottom offset:
+ left offset:
+
+ Control side offsets
+ Set a custom offset to each side of the screen
+ Video and renderer
+ Resolution, scaling type, and renderer
+ Control customization
+ Gestures types, triggers, and scaling
+ Java Tweaks
+ Java versions, JVM Arguments, and RAM amount
+ Miscellaneous settings
+ Version list, and libs checks
+ Experimental Stuff
+ Use things there with consideration, no support.
+
+ Back to the last screen
+
diff --git a/app_pojavlauncher/src/main/res/xml/pref_control.xml b/app_pojavlauncher/src/main/res/xml/pref_control.xml
new file mode 100644
index 0000000000..720e1992ea
--- /dev/null
+++ b/app_pojavlauncher/src/main/res/xml/pref_control.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app_pojavlauncher/src/main/res/xml/pref_experimental.xml b/app_pojavlauncher/src/main/res/xml/pref_experimental.xml
new file mode 100644
index 0000000000..851b002692
--- /dev/null
+++ b/app_pojavlauncher/src/main/res/xml/pref_experimental.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app_pojavlauncher/src/main/res/xml/pref_java.xml b/app_pojavlauncher/src/main/res/xml/pref_java.xml
new file mode 100644
index 0000000000..d585dd1f4d
--- /dev/null
+++ b/app_pojavlauncher/src/main/res/xml/pref_java.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app_pojavlauncher/src/main/res/xml/pref_main.xml b/app_pojavlauncher/src/main/res/xml/pref_main.xml
index 9c811585dd..6fc8015d16 100644
--- a/app_pojavlauncher/src/main/res/xml/pref_main.xml
+++ b/app_pojavlauncher/src/main/res/xml/pref_main.xml
@@ -1,129 +1,39 @@
-
-
-
-
+
+
-
-
+
-
+
+
-
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/app_pojavlauncher/src/main/res/xml/pref_misc.xml b/app_pojavlauncher/src/main/res/xml/pref_misc.xml
new file mode 100644
index 0000000000..eaf7a166cb
--- /dev/null
+++ b/app_pojavlauncher/src/main/res/xml/pref_misc.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app_pojavlauncher/src/main/res/xml/pref_video.xml b/app_pojavlauncher/src/main/res/xml/pref_video.xml
new file mode 100644
index 0000000000..b05ee7d92b
--- /dev/null
+++ b/app_pojavlauncher/src/main/res/xml/pref_video.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jre_lwjgl3glfw/src/main/java/org/lwjgl/glfw/CallbackBridge.java b/jre_lwjgl3glfw/src/main/java/org/lwjgl/glfw/CallbackBridge.java
index c2f9214c9f..ba94e5c3f8 100644
--- a/jre_lwjgl3glfw/src/main/java/org/lwjgl/glfw/CallbackBridge.java
+++ b/jre_lwjgl3glfw/src/main/java/org/lwjgl/glfw/CallbackBridge.java
@@ -53,7 +53,7 @@ public static void sendGrabbing(boolean grab, int xset, int yset) {
}
// Called from Android side
- public static void receiveCallback(int type, int i1, int i2, int i3, int i4) {
+ public static void receiveCallback(int type, float i1, float i2, int i3, int i4) {
/*
if (INPUT_DEBUG_ENABLED) {
System.out.println("LWJGL GLFW Callback received type=" + Integer.toString(type) + ", data=" + i1 + ", " + i2 + ", " + i3 + ", " + i4);
@@ -61,10 +61,10 @@ public static void receiveCallback(int type, int i1, int i2, int i3, int i4) {
*/
if (PENDING_EVENT_READY) {
if (type == EVENT_TYPE_CURSOR_POS) {
- GLFW.mGLFWCursorX = i1;
- GLFW.mGLFWCursorY = i2;
+ GLFW.mGLFWCursorX = (double) i1;
+ GLFW.mGLFWCursorY = (double) i2;
} else {
- PENDING_EVENT_LIST.add(new Integer[]{type, i1, i2, i3, i4});
+ PENDING_EVENT_LIST.add(new Integer[]{type, (int) i1, (int)i2, i3, i4});
}
} // else System.out.println("Event input is not ready yet!");
}