/* * Copyright 2000-2013 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.ide.ui.laf; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Factory; import com.intellij.openapi.util.registry.Registry; import com.intellij.ui.Gray; import com.intellij.ui.JBColor; import com.intellij.ui.LightColors; import com.intellij.util.ui.*; import consulo.util.ui.OwnScrollBarUI; import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicScrollBarUI; import java.awt.*; import java.awt.event.*; /** * @author max * @author Konstantin Bulenkov */ public class IntelliJButtonlessScrollBarUI extends BasicScrollBarUI implements OwnScrollBarUI { @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass", "UnusedDeclaration"}) public static ComponentUI createUI(JComponent c) { return new IntelliJButtonlessScrollBarUI(); } public static JBColor getGradientLightColor() { return new JBColor(Gray._251, Gray._95); } public static JBColor getGradientDarkColor() { return new JBColor(Gray._215, Gray._80); } private static JBColor getGradientThumbBorderColor() { return new JBColor(Gray._201, Gray._85); } public static JBColor getTrackBackground() { return new JBColor(LightColors.SLIGHTLY_GRAY, UIUtil.getListBackground()); } public static JBColor getTrackBorderColor() { return new JBColor(Gray._230, UIUtil.getListBackground()); } private static final BasicStroke BORDER_STROKE = new BasicStroke(); private static int getAnimationColorShift() { return UIUtil.isUnderDarcula() ? 20 : 40; } private static Factory<JButton> EMPTY_BUTTON_FACTORY = new Factory<JButton>() { @Override public JButton create() { return new EmptyButton(); } }; private final AdjustmentListener myAdjustmentListener; private final MouseMotionAdapter myMouseMotionListener; private final MouseAdapter myMouseListener; private Factory<JButton> myIncreaseButtonFactory = EMPTY_BUTTON_FACTORY; private Animator myAnimator; private int myAnimationColorShift = 0; private boolean myMouseIsOverThumb = false; public static final int DELAY_FRAMES = 4; public static final int FRAMES_COUNT = 10 + DELAY_FRAMES; protected IntelliJButtonlessScrollBarUI() { myAdjustmentListener = new AdjustmentListener() { @Override public void adjustmentValueChanged(AdjustmentEvent e) { resetAnimator(); } }; myMouseMotionListener = new MouseMotionAdapter() { @Override public void mouseMoved(MouseEvent e) { boolean inside = isOverThumb(e.getPoint()); if (inside != myMouseIsOverThumb) { myMouseIsOverThumb = inside; resetAnimator(); } } }; myMouseListener = new MouseAdapter() { @Override public void mouseExited(MouseEvent e) { if (myMouseIsOverThumb) { myMouseIsOverThumb = false; resetAnimator(); } } }; } @Override public void layoutContainer(Container scrollbarContainer) { try { super.layoutContainer(scrollbarContainer); } catch (NullPointerException ignore) { //installUI is not performed yet or uninstallUI has set almost every field to null. Just ignore it //IDEA-89674 } } @Override protected ModelListener createModelListener() { return new ModelListener() { @Override public void stateChanged(ChangeEvent e) { if (scrollbar != null) { super.stateChanged(e); } } }; } public int getDecrementButtonHeight() { return decrButton.getHeight(); } public int getIncrementButtonHeight() { return incrButton.getHeight(); } private void resetAnimator() { myAnimator.reset(); if (scrollbar != null && scrollbar.getValueIsAdjusting() || myMouseIsOverThumb || Registry.is("ui.no.bangs.and.whistles")) { myAnimator.suspend(); myAnimationColorShift = getAnimationColorShift(); } else { myAnimator.resume(); } } public static BasicScrollBarUI createNormal() { return new IntelliJButtonlessScrollBarUI(); } @Override public void installUI(JComponent c) { super.installUI(c); scrollbar.setFocusable(false); } @Override protected void installDefaults() { final int incGap = UIManager.getInt("ScrollBar.incrementButtonGap"); final int decGap = UIManager.getInt("ScrollBar.decrementButtonGap"); try { UIManager.put("ScrollBar.incrementButtonGap", 0); UIManager.put("ScrollBar.decrementButtonGap", 0); super.installDefaults(); } finally { UIManager.put("ScrollBar.incrementButtonGap", incGap); UIManager.put("ScrollBar.decrementButtonGap", decGap); } } @Override protected void installListeners() { if (myAnimator == null || myAnimator.isDisposed()) { myAnimator = createAnimator(); } super.installListeners(); scrollbar.addAdjustmentListener(myAdjustmentListener); scrollbar.addMouseListener(myMouseListener); scrollbar.addMouseMotionListener(myMouseMotionListener); } private Animator createAnimator() { return new Animator("Adjustment fadeout", FRAMES_COUNT, FRAMES_COUNT * 50, false) { @Override public void paintNow(int frame, int totalFrames, int cycle) { myAnimationColorShift = getAnimationColorShift(); if (frame > DELAY_FRAMES) { myAnimationColorShift *= 1 - ((double)(frame - DELAY_FRAMES)) / ((double)(totalFrames - DELAY_FRAMES)); } if (scrollbar != null) { scrollbar.repaint(((IntelliJButtonlessScrollBarUI)scrollbar.getUI()).getThumbBounds()); } } }; } private boolean isOverThumb(Point p) { final Rectangle bounds = getThumbBounds(); return bounds != null && bounds.contains(p); } @Override public Rectangle getThumbBounds() { return super.getThumbBounds(); } @Override protected void uninstallListeners() { if (scrollTimer != null) { // it is already called otherwise super.uninstallListeners(); } scrollbar.removeAdjustmentListener(myAdjustmentListener); Disposer.dispose(myAnimator); } @Override protected void paintTrack(Graphics g, JComponent c, Rectangle bounds) { g.setColor(getTrackBackground()); g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); g.setColor(getTrackBorderColor()); if (isVertical()) { g.drawLine(bounds.x, bounds.y, bounds.x, bounds.y + bounds.height); } else { g.drawLine(bounds.x, bounds.y, bounds.x + bounds.width, bounds.y); } RegionPainter<Object> painter = UIUtil.getClientProperty(c, TRACK); if (painter != null) { painter.paint((Graphics2D)g, bounds.x, bounds.y, bounds.width, bounds.height, null); } } @Override protected Dimension getMinimumThumbSize() { final int thickness = getThickness(); return isVertical() ? new Dimension(thickness, thickness * 2) : new Dimension(thickness * 2, thickness); } protected int getThickness() { return JBUI.scale(13); } @Override public Dimension getMaximumSize(JComponent c) { int thickness = getThickness(); return new Dimension(thickness, thickness); } @Override public Dimension getMinimumSize(JComponent c) { return getMaximumSize(c); } @Override public Dimension getPreferredSize(JComponent c) { return getMaximumSize(c); } @Override protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) { if (thumbBounds.isEmpty() || !scrollbar.isEnabled()) { return; } g.translate(thumbBounds.x, thumbBounds.y); paintMaxiThumb((Graphics2D)g, thumbBounds); g.translate(-thumbBounds.x, -thumbBounds.y); } private void paintMaxiThumb(Graphics2D g, Rectangle thumbBounds) { final boolean vertical = isVertical(); int hGap = JBUI.scale(vertical ? 2 : 1); int vGap = JBUI.scale(vertical ? 1 : 2); int w = adjustThumbWidth(thumbBounds.width - hGap * 2); int h = thumbBounds.height - vGap * 2; // leave one pixel between thumb and right or bottom edge if (vertical) { h -= JBUI.scale(1); } else { w -= JBUI.scale(1); } final Paint paint; final Color start = adjustColor(getGradientLightColor()); final Color end = adjustColor(getGradientDarkColor()); if (vertical) { paint = UIUtil.getGradientPaint(JBUI.scale(1), 0, start, w + JBUI.scale(1), 0, end); } else { paint = UIUtil.getGradientPaint(0, JBUI.scale(1), start, 0, h + JBUI.scale(1), end); } g.setPaint(paint); g.fillRect(hGap + JBUI.scale(1), vGap + JBUI.scale(1), w - JBUI.scale(1), h - JBUI.scale(1)); final Stroke stroke = g.getStroke(); g.setStroke(BORDER_STROKE); g.setColor(getGradientThumbBorderColor()); g.drawRoundRect(hGap, vGap, w, h, JBUI.scale(3), JBUI.scale(3)); g.setStroke(stroke); } @Override public boolean getSupportsAbsolutePositioning() { return true; } protected int adjustThumbWidth(int width) { return width; } protected Color adjustColor(Color c) { if (myAnimationColorShift == 0) return c; final int sign = UIUtil.isUnderDarcula() ? -1 : 1; return Gray.get(Math.max(0, Math.min(255, c.getRed() - sign * myAnimationColorShift))); } private boolean isVertical() { return scrollbar.getOrientation() == Adjustable.VERTICAL; } @Override protected JButton createIncreaseButton(int orientation) { return myIncreaseButtonFactory.create(); } @Override protected JButton createDecreaseButton(int orientation) { return new EmptyButton(); } @Override public void setIncreaseButtonFactory(@NotNull Factory<JButton> buttonFactory) { myIncreaseButtonFactory = buttonFactory; } private static class EmptyButton extends JButton { private EmptyButton() { setFocusable(false); setRequestFocusEnabled(false); } @Override public Dimension getMaximumSize() { return new Dimension(0, 0); } @Override public Dimension getPreferredSize() { return getMaximumSize(); } @Override public Dimension getMinimumSize() { return getMaximumSize(); } } }