diff --git a/bin/idea.properties b/bin/idea.properties index 3e178cb85f00..cd6960d75f5c 100644 --- a/bin/idea.properties +++ b/bin/idea.properties @@ -130,13 +130,30 @@ sun.java2d.uiScale.enabled=false # Although Java 7 introduced MouseWheelEven.getPreciseWheelRotation() method, JScrollPane doesn't use it so far. # Depends on the Editor / General / Smooth Scrolling setting, remote desktop detection and power save mode state. # Ideally, we need to patch the runtime (on Windows, Linux and Mac OS) to improve handling of the fine-grained input data. +# This feature can be toggled via "idea.true.smooth.scrolling.high.precision" option. # -# * Enables interpolation of scrolling input (scrollbar thumb, mouse wheel, etc). +# * Enables handling of pixel-perfect scrolling events. +# Currently this mode is available only under Mac OS with JetBrains Runtime. +# This feature can be toggled via "idea.true.smooth.scrolling.pixel.perfect" option. +# +# * Enables interpolation of scrolling input (scrollbar, mouse wheel, touchpad, keys, etc). # Smooths input which lacks both spatial and temporal resolution, performs the rendering asynchronously. # Depends on the Editor / General / Smooth Scrolling setting, remote desktop detection and power save mode state. +# The feature can be tweaked using the following options: +# "idea.true.smooth.scrolling.interpolation" - the main switch +# "idea.true.smooth.scrolling.interpolation.scrollbar" - scrollbar interpolation +# "idea.true.smooth.scrolling.interpolation.scrollbar.delay" - initial delay for scrollbar interpolation (ms) +# "idea.true.smooth.scrolling.interpolation.mouse.wheel" - mouse wheel / touchpad interpolation +# "idea.true.smooth.scrolling.interpolation.mouse.wheel.delay.min" - minimum initial delay for mouse wheel interpolation (ms) +# "idea.true.smooth.scrolling.interpolation.mouse.wheel.delay.max" - maximum initial delay for mouse wheel interpolation (ms) +# "idea.true.smooth.scrolling.interpolation.precision.touchpad" - precision touchpad interpolation +# "idea.true.smooth.scrolling.interpolation.precision.touchpad.delay" - initial delay for precision touchpad interpolation (ms) +# "idea.true.smooth.scrolling.interpolation.other" - interpolation of other input sources +# "idea.true.smooth.scrolling.interpolation.other.delay" - initial delay for other input source interpolation (ms) # # * Adds on-demand horizontal scrollbar in editor. # The horizontal scrollbar is shown only when it's actually needed for currently visible content. # This helps to save editor space and to prevent occasional horizontal "jitter" on vertical touchpad scrolling. +# This feature can be toggled via "idea.true.smooth.scrolling.dynamic.scrollbars" option. #----------------------------------------------------------------------- #idea.true.smooth.scrolling=true diff --git a/platform/core-api/src/com/intellij/ui/ComponentSettings.java b/platform/core-api/src/com/intellij/ui/ComponentSettings.java index 05028add5d29..fe0e95f17543 100644 --- a/platform/core-api/src/com/intellij/ui/ComponentSettings.java +++ b/platform/core-api/src/com/intellij/ui/ComponentSettings.java @@ -16,8 +16,9 @@ package com.intellij.ui; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.util.registry.RegistryValue; import com.intellij.util.SystemProperties; -import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -26,6 +27,18 @@ import java.awt.*; * IDE-agnostic component settings. */ public class ComponentSettings { + private static final RegistryValue HIGH_PRECISION_SCROLLING = Registry.get("idea.true.smooth.scrolling.high.precision"); + + private static final RegistryValue PIXEL_PERFECT_SCROLLING = Registry.get("idea.true.smooth.scrolling.pixel.perfect"); + + private static final RegistryValue SCROLLING_INTERPOLATION = Registry.get("idea.true.smooth.scrolling.interpolation"); + private static final RegistryValue SCROLLBAR_INTERPOLATION = Registry.get("idea.true.smooth.scrolling.interpolation.scrollbar"); + private static final RegistryValue MOUSE_WHEEL_INTERPOLATION = Registry.get("idea.true.smooth.scrolling.interpolation.mouse.wheel"); + private static final RegistryValue PRECISION_TOUCHPAD_INTERPOLATION = Registry.get("idea.true.smooth.scrolling.interpolation.precision.touchpad"); + private static final RegistryValue OTHER_SOURCES_INTERPOLATION = Registry.get("idea.true.smooth.scrolling.interpolation.other"); + + private static final RegistryValue DYNAMIC_SCROLLBARS = Registry.get("idea.true.smooth.scrolling.dynamic.scrollbars"); + private boolean mySmoothScrollingEnabled = true; private boolean myRemoteDesktopConnected; private boolean myPowerSaveModeEnabled; @@ -36,7 +49,8 @@ public class ComponentSettings { return ourInstance; } - public boolean isSmoothScrollingEligibleFor(Component component) { + // Returns whether "true smooth scrolling" is applicable to the particular component + public boolean isTrueSmoothScrollingEligibleFor(Component component) { return SystemProperties.isTrueSmoothScrollingEnabled() && !ApplicationManager.getApplication().isUnitTestMode() && mySmoothScrollingEnabled && @@ -46,6 +60,39 @@ public class ComponentSettings { component.isShowing(); } + // Returns whether high-precision scrolling events are enabled + public boolean isHighPrecisionScrollingEnabled() { + return HIGH_PRECISION_SCROLLING.asBoolean(); + } + + // Returns whether pixel-perfect scrolling events are enabled (requires high-precision events to be effective) + public boolean isPixelPerfectScrollingEnabled() { + return PIXEL_PERFECT_SCROLLING.asBoolean(); + } + + // Returns whether scrolling interpolation is enabled for particular input source + public boolean isInterpolationEnabledFor(InputSource source) { + if (!SCROLLING_INTERPOLATION.asBoolean()) { + return false; + } + + switch (source) { + case SCROLLBAR: + return SCROLLBAR_INTERPOLATION.asBoolean(); + case MOUSE_WHEEL: + return MOUSE_WHEEL_INTERPOLATION.asBoolean(); + case PRECISION_TOUCHPAD: + return PRECISION_TOUCHPAD_INTERPOLATION.asBoolean(); + default: + return OTHER_SOURCES_INTERPOLATION.asBoolean(); + } + } + + // Returns whether dymaics scrollbars are enabled (currently applies only to editor's horizontal scrollbar) + public boolean areDynamicScrollbarsEnabled() { + return DYNAMIC_SCROLLBARS.asBoolean(); + } + /* A heuristics that disables scrolling interpolation in diff / merge windows. We need to to make scrolling synchronization compatible with the interpolation first. diff --git a/platform/core-api/src/com/intellij/ui/InputSource.java b/platform/core-api/src/com/intellij/ui/InputSource.java new file mode 100644 index 000000000000..8bb6b286eda5 --- /dev/null +++ b/platform/core-api/src/com/intellij/ui/InputSource.java @@ -0,0 +1,26 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.ui; + +/** + * Source of input event. + */ +public enum InputSource { + SCROLLBAR, + PRECISION_TOUCHPAD, + MOUSE_WHEEL, + UNKNOWN +} diff --git a/platform/platform-api/src/com/intellij/ui/components/JBScrollPane.java b/platform/platform-api/src/com/intellij/ui/components/JBScrollPane.java index 4aa18236e3d8..f9becb45a841 100644 --- a/platform/platform-api/src/com/intellij/ui/components/JBScrollPane.java +++ b/platform/platform-api/src/com/intellij/ui/components/JBScrollPane.java @@ -20,6 +20,7 @@ import com.intellij.openapi.util.Key; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.wm.IdeGlassPane; +import com.intellij.ui.ComponentSettings; import com.intellij.ui.IdeBorderFactory; import com.intellij.util.ArrayUtil; import com.intellij.util.ReflectionUtil; @@ -744,9 +745,10 @@ public class JBScrollPane extends SmoothScrollPane { if (event.isConsumed()) return false; // any rotation expected (forward or backward) boolean ignore = event.getWheelRotation() == 0; - if (ignore && (isPreciseRotationSupported() || isTrueSmoothScrollingEnabled())) { + if (ignore && (isPreciseRotationSupported() || + (isTrueSmoothScrollingEnabled() && ComponentSettings.getInstance().isHighPrecisionScrollingEnabled()))) { double rotation = event.getPreciseWheelRotation(); - double delta = MouseWheelEventEx.getScrollingDelta(event); + double delta = MouseWheelEventEx.getAbsoluteDelta(event); ignore = (rotation == 0.0D || !Double.isFinite(rotation)) && (delta == 0.0D || !Double.isFinite(delta)); } return !ignore && 0 == (SCROLL_MODIFIERS & event.getModifiers()); diff --git a/platform/platform-api/src/com/intellij/ui/components/MouseWheelEventEx.java b/platform/platform-api/src/com/intellij/ui/components/MouseWheelEventEx.java index 436787e8f4f8..e5325923a228 100644 --- a/platform/platform-api/src/com/intellij/ui/components/MouseWheelEventEx.java +++ b/platform/platform-api/src/com/intellij/ui/components/MouseWheelEventEx.java @@ -15,6 +15,9 @@ */ package com.intellij.ui.components; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.ui.ComponentSettings; + import java.awt.event.MouseWheelEvent; /** @@ -44,8 +47,15 @@ class MouseWheelEventEx { * @return negative values for scrolling up, positive values for scrolling down * 0.0 when absolute deltas are not supported or not available */ - static double getScrollingDelta(MouseWheelEvent e) { - // This API is temporary disabled as there's no way to detect relative deltas. - return 0.0D;//SystemInfo.isJetbrainsJvm && SystemInfo.isMac ? 10.0D * e.getPreciseWheelRotation() : 0.0D; + static double getAbsoluteDelta(MouseWheelEvent e) { + ComponentSettings settings = ComponentSettings.getInstance(); + + return settings.isPixelPerfectScrollingEnabled() + ? areAbsoluteDeltasSupported() ? 10.0D * e.getPreciseWheelRotation() : 0.0D + : 0.0D; + } + + private static boolean areAbsoluteDeltasSupported() { + return SystemInfo.isJetbrainsJvm && SystemInfo.isMac; } } diff --git a/platform/platform-api/src/com/intellij/ui/components/SmoothScrollPane.java b/platform/platform-api/src/com/intellij/ui/components/SmoothScrollPane.java index 41ff944a68fc..e891c87e771c 100644 --- a/platform/platform-api/src/com/intellij/ui/components/SmoothScrollPane.java +++ b/platform/platform-api/src/com/intellij/ui/components/SmoothScrollPane.java @@ -16,7 +16,10 @@ package com.intellij.ui.components; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.util.registry.RegistryValue; import com.intellij.ui.ComponentSettings; +import com.intellij.ui.InputSource; import com.intellij.util.SystemProperties; import org.jetbrains.annotations.Nullable; @@ -31,29 +34,20 @@ import java.lang.reflect.Field; import static java.lang.Math.*; /** - * Scroll pane that supports high-precision mouse wheel events and input interpolation. + * Scroll pane that supports high-precision mouse wheel events (including pixel-perfect ones) and input interpolation. */ public class SmoothScrollPane extends JScrollPane { private static final Logger LOG = Logger.getInstance(SmoothScrollPane.class); private static final double EPSILON = 1E-5D; - // We may enhance the value derivation (make it distance-dependent, etc), - // additionally, we may add these values to the Registry. - private static final int THUMB_DELAY = 50; // ms - private static final int PRECISION_TOUCHPAD_DELAY = 40; // ms - private static final int WHEEL_MIN_DELAY = 60; // ms - private static final int WHEEL_MAX_DELAY = 140; // ms - private static final int DEFAULT_DELAY = 120; // ms - - private enum InputSource { - SCROLLBAR_THUMB, - MOUSE_WHEEL, - UNKNOWN - } + private static final RegistryValue SCROLLBAR_DELAY = Registry.get("idea.true.smooth.scrolling.interpolation.scrollbar.delay"); + private static final RegistryValue PRECISION_TOUCHPAD_DELAY = Registry.get("idea.true.smooth.scrolling.interpolation.precision.touchpad.delay"); + private static final RegistryValue MOUSE_WHEEL_MIN_DELAY = Registry.get("idea.true.smooth.scrolling.interpolation.mouse.wheel.delay.min"); + private static final RegistryValue MOUSE_WHEEL_MAX_DELAY = Registry.get("idea.true.smooth.scrolling.interpolation.mouse.wheel.delay.max"); + private static final RegistryValue DEFAULT_DELAY = Registry.get("idea.true.smooth.scrolling.interpolation.other.delay"); private InputSource myInputSource = InputSource.UNKNOWN; private double myWheelRotation; - private double myScrollingDelta; public SmoothScrollPane() { } @@ -72,9 +66,10 @@ public class SmoothScrollPane extends JScrollPane { @Override protected void processMouseWheelEvent(MouseWheelEvent e) { - myInputSource = InputSource.MOUSE_WHEEL; + boolean hasAbsoluteDelta = ComponentSettings.getInstance().isPixelPerfectScrollingEnabled() && + MouseWheelEventEx.getAbsoluteDelta(e) != 0.0D; + myInputSource = hasAbsoluteDelta ? InputSource.PRECISION_TOUCHPAD : InputSource.MOUSE_WHEEL; myWheelRotation = e.getPreciseWheelRotation(); - myScrollingDelta = MouseWheelEventEx.getScrollingDelta(e); super.processMouseWheelEvent(e); myInputSource = InputSource.UNKNOWN; } @@ -115,7 +110,10 @@ public class SmoothScrollPane extends JScrollPane { } private void handleMouseWheelEvent(MouseWheelEvent e, MouseWheelListener delegate) { - if (ComponentSettings.getInstance().isSmoothScrollingEligibleFor(this) && + ComponentSettings settings = ComponentSettings.getInstance(); + + if (settings.isTrueSmoothScrollingEligibleFor(this) && + settings.isHighPrecisionScrollingEnabled() && isWheelScrollingEnabled() && e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) { mouseWheelMoved(e); @@ -141,7 +139,7 @@ public class SmoothScrollPane extends JScrollPane { private void mouseWheelMoved(MouseWheelEvent e) { JScrollBar scrollbar = e.isShiftDown() ? getHorizontalScrollBar() : getVerticalScrollBar(); - double delta = MouseWheelEventEx.getScrollingDelta(e); + double delta = MouseWheelEventEx.getAbsoluteDelta(e); if (delta == 0.0D) { delta = getRelativeDelta(e, scrollbar); } @@ -197,17 +195,22 @@ public class SmoothScrollPane extends JScrollPane { return viewport != null && (viewport.getView() instanceof Scrollable) ? (Scrollable)(viewport.getView()) : null; } - public int getInitialDelay(boolean valueIsAdjusting) { - InputSource source = valueIsAdjusting ? InputSource.SCROLLBAR_THUMB : myInputSource; + public InputSource getInputSource(boolean valueIsAdjusting) { + return valueIsAdjusting ? InputSource.SCROLLBAR : myInputSource; + } + public int getInitialDelay(InputSource source) { switch (source) { - case SCROLLBAR_THUMB: - return THUMB_DELAY; + case SCROLLBAR: + return SCROLLBAR_DELAY.asInteger(); + case PRECISION_TOUCHPAD: + return PRECISION_TOUCHPAD_DELAY.asInteger(); case MOUSE_WHEEL: - if (myScrollingDelta != 0.0D) return PRECISION_TOUCHPAD_DELAY; - return max(WHEEL_MIN_DELAY, min((int)round(abs(myWheelRotation) * WHEEL_MAX_DELAY), WHEEL_MAX_DELAY)); + return max(MOUSE_WHEEL_MIN_DELAY.asInteger(), + min((int)round(abs(myWheelRotation) * MOUSE_WHEEL_MAX_DELAY.asInteger()), + MOUSE_WHEEL_MAX_DELAY.asInteger())); default: - return DEFAULT_DELAY; + return DEFAULT_DELAY.asInteger(); } } @@ -225,8 +228,14 @@ public class SmoothScrollPane extends JScrollPane { @Override public void setValue(int value) { ComponentSettings settings = ComponentSettings.getInstance(); - if (settings.isSmoothScrollingEligibleFor(getViewport().getView()) && settings.isInterpolationEligibleFor(this)) { - myInterpolator.setTarget(value, getInitialDelay(getValueIsAdjusting())); + + InputSource source = getInputSource(getValueIsAdjusting()); + + if (settings.isTrueSmoothScrollingEligibleFor(getViewport().getView()) && + settings.isInterpolationEligibleFor(this) && + settings.isInterpolationEnabledFor(source)) { + + myInterpolator.setTarget(value, getInitialDelay(source)); } else { super.setValue(value); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index 33f97d7787b3..250c7fabfb91 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -1864,7 +1864,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi public Dimension getPreferredSize() { return isReleased ? new Dimension() - : SystemProperties.isTrueSmoothScrollingEnabled() + : SystemProperties.isTrueSmoothScrollingEnabled() && ComponentSettings.getInstance().areDynamicScrollbarsEnabled() ? new Dimension(getPreferredWidthOfVisibleLines(), myView.getPreferredHeight()) : myView.getPreferredSize(); } @@ -2794,10 +2794,17 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi @Override public void setValue(int value) { ComponentSettings settings = ComponentSettings.getInstance(); - if (settings.isSmoothScrollingEligibleFor(myEditorComponent) && + + MyScrollPane scrollPane = (MyScrollPane)myScrollPane; + + InputSource source = scrollPane.getInputSource(getValueIsAdjusting()); + + if (settings.isTrueSmoothScrollingEligibleFor(myEditorComponent) && settings.isInterpolationEligibleFor(this) && + settings.isInterpolationEnabledFor(source) && myScrollingModel.isAnimationEnabled()) { - myInterpolator.setTarget(value, ((MyScrollPane)myScrollPane).getInitialDelay(getValueIsAdjusting())); + + myInterpolator.setTarget(value, scrollPane.getInitialDelay(source)); } else { super.setValue(value); diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 74213e628e68..c8097454972c 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -700,6 +700,45 @@ editor.zero.latency.rendering.debug.description=Make a pause after immediate ren idea.true.smooth.scrolling.debug=false idea.true.smooth.scrolling.debug.description=Check window blitter and true double buffering utilization +idea.true.smooth.scrolling.high.precision=true +idea.true.smooth.scrolling.high.precision.description=Enables high-precision scrolling + +idea.true.smooth.scrolling.pixel.perfect=true +idea.true.smooth.scrolling.pixel.perfect.description=Enables pixel-perfect scrolling + +idea.true.smooth.scrolling.interpolation=true +idea.true.smooth.scrolling.interpolation.description=Enables scrolling interpolation (smooth scrolling) in general + +idea.true.smooth.scrolling.interpolation.scrollbar=true +idea.true.smooth.scrolling.interpolation.scrollbar.description=Enables scrolling interpolation for scrollbars + +idea.true.smooth.scrolling.interpolation.scrollbar.delay=30 +idea.true.smooth.scrolling.interpolation.scrollbar.delay.description=Initial delay for scrollbar interpolation (ms) + +idea.true.smooth.scrolling.interpolation.mouse.wheel=true +idea.true.smooth.scrolling.interpolation.mouse.wheel.description=Enables scrolling interpolation for mouse wheel / touchpad + +idea.true.smooth.scrolling.interpolation.mouse.wheel.delay.min=60 +idea.true.smooth.scrolling.interpolation.mouse.wheel.delay.min.description=Minimum initial delay for mouse wheel interpolation (ms) + +idea.true.smooth.scrolling.interpolation.mouse.wheel.delay.max=140 +idea.true.smooth.scrolling.interpolation.mouse.wheel.delay.max.description=Maximum initial delay for mouse wheel interpolation (ms) + +idea.true.smooth.scrolling.interpolation.precision.touchpad=true +idea.true.smooth.scrolling.interpolation.precision.touchpad.description=Enables scrolling interpolation of pixel-perfect scrolling + +idea.true.smooth.scrolling.interpolation.precision.touchpad.delay=20 +idea.true.smooth.scrolling.interpolation.precision.touchpad.delay.description=Initial delay for pixel-perfect scrolling interpolation (ms) + +idea.true.smooth.scrolling.interpolation.other=true +idea.true.smooth.scrolling.interpolation.other.description=Enables scrolling interpolation for other input sources (keyboard, etc.) + +idea.true.smooth.scrolling.interpolation.other.delay=120 +idea.true.smooth.scrolling.interpolation.other.delay.description=Initial delay for other input source interpolation (ms) + +idea.true.smooth.scrolling.dynamic.scrollbars=true +idea.true.smooth.scrolling.dynamic.scrollbars.description=Show scrollbars only when they are needed for currently visible content + decompiler.use.line.mapping=true decompiler.use.line.mapping.description=Maps original to decompiled line numbers when stepping in debugger. decompiler.dump.original.lines=false