mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-149395 New look for scrollbars on Windows and Linux
This commit is contained in:
@@ -0,0 +1,509 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.components;
|
||||
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.ui.components.JBScrollBar.Alignment;
|
||||
import com.intellij.util.ui.JBInsets;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.RegionPainter;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.plaf.ScrollBarUI;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
|
||||
import static java.awt.Adjustable.VERTICAL;
|
||||
|
||||
/**
|
||||
* @author Sergey.Malenkov
|
||||
*/
|
||||
abstract class AbstractScrollBarUI extends ScrollBarUI {
|
||||
static final Key<RegionPainter<Object>> LEADING_AREA = Key.create("PLAIN_SCROLL_BAR_UI_LEADING_AREA");//TODO:support
|
||||
|
||||
private static final JBColor TRACK_BACKGROUND = new JBColor(0xF5F5F5, 0x3c3f41);
|
||||
private static final JBColor TRACK_FOREGROUND = new JBColor(0xE6E6E6, 0x3C3F41);
|
||||
|
||||
private final Listener myListener = new Listener();
|
||||
private final Timer myScrollTimer = new Timer(60, myListener);
|
||||
|
||||
private final Rectangle myThumbBounds = new Rectangle();
|
||||
private final Rectangle myTrackBounds = new Rectangle();
|
||||
private final Rectangle myLeadingBounds = new Rectangle();
|
||||
|
||||
private JScrollBar myScrollBar;
|
||||
|
||||
private boolean isTrackVisible;
|
||||
private boolean isValueCached;
|
||||
private int myCachedValue;
|
||||
|
||||
abstract int getThickness();
|
||||
|
||||
abstract int getMinimalThickness();
|
||||
|
||||
abstract void onTrackHover(boolean hover);
|
||||
|
||||
abstract void onThumbHover(boolean hover);
|
||||
|
||||
abstract void paintTrack(Graphics2D g, int x, int y, int width, int height, JComponent c);
|
||||
|
||||
abstract void paintThumb(Graphics2D g, int x, int y, int width, int height, JComponent c);
|
||||
|
||||
void setTrackVisible(boolean trackVisible) {
|
||||
if (isTrackVisible != trackVisible) {
|
||||
isTrackVisible = trackVisible;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void repaint() {
|
||||
if (myScrollBar != null) myScrollBar.repaint();
|
||||
}
|
||||
|
||||
void repaint(int x, int y, int width, int height) {
|
||||
if (myScrollBar != null) myScrollBar.repaint(x, y, width, height);
|
||||
}
|
||||
|
||||
int scale(int value) {
|
||||
value = JBUI.scale(value);
|
||||
//noinspection EnumSwitchStatementWhichMissesCases
|
||||
switch (UIUtil.getComponentStyle(myScrollBar)) {
|
||||
case LARGE:
|
||||
return (int)(value * 1.15);
|
||||
case SMALL:
|
||||
return (int)(value * 0.857);
|
||||
case MINI:
|
||||
return (int)(value * 0.714);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installUI(JComponent c) {
|
||||
myScrollBar = (JScrollBar)c;
|
||||
myScrollBar.setBackground(TRACK_BACKGROUND);
|
||||
myScrollBar.setForeground(TRACK_FOREGROUND);
|
||||
myScrollBar.setFocusable(false);
|
||||
myScrollBar.addMouseListener(myListener);
|
||||
myScrollBar.addMouseMotionListener(myListener);
|
||||
myScrollBar.getModel().addChangeListener(myListener);
|
||||
myScrollBar.addPropertyChangeListener(myListener);
|
||||
myScrollBar.addFocusListener(myListener);
|
||||
myScrollTimer.setInitialDelay(300);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninstallUI(JComponent c) {
|
||||
myScrollTimer.stop();
|
||||
myScrollBar.removeFocusListener(myListener);
|
||||
myScrollBar.removePropertyChangeListener(myListener);
|
||||
myScrollBar.getModel().removeChangeListener(myListener);
|
||||
myScrollBar.removeMouseMotionListener(myListener);
|
||||
myScrollBar.removeMouseListener(myListener);
|
||||
myScrollBar = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize(JComponent c) {
|
||||
int thickness = getThickness();
|
||||
Alignment alignment = Alignment.get(c);
|
||||
return alignment == Alignment.LEFT || alignment == Alignment.RIGHT
|
||||
? new Dimension(thickness, thickness * 3)
|
||||
: new Dimension(thickness * 3, thickness);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g, JComponent c) {
|
||||
Alignment alignment = Alignment.get(c);
|
||||
if (alignment != null && g instanceof Graphics2D) {
|
||||
Rectangle bounds = new Rectangle(c.getSize());
|
||||
JBInsets.removeFrom(bounds, c.getInsets());
|
||||
if (c.isOpaque() && c.getParent() instanceof JScrollPane) {
|
||||
g.setColor(c.getForeground());
|
||||
switch (alignment) {
|
||||
case TOP:
|
||||
bounds.height--;
|
||||
g.drawLine(bounds.x, bounds.y + bounds.height, bounds.x + bounds.width, bounds.y + bounds.height);
|
||||
break;
|
||||
case LEFT:
|
||||
bounds.width--;
|
||||
g.drawLine(bounds.x + bounds.width, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height);
|
||||
break;
|
||||
case RIGHT:
|
||||
g.drawLine(bounds.x, bounds.y, bounds.x, bounds.y + bounds.height);
|
||||
bounds.width--;
|
||||
bounds.x++;
|
||||
break;
|
||||
case BOTTOM:
|
||||
g.drawLine(bounds.x, bounds.y, bounds.x + bounds.width, bounds.y);
|
||||
bounds.height--;
|
||||
bounds.y++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (isTrackVisible) {
|
||||
paintTrack((Graphics2D)g, bounds.x, bounds.y, bounds.width, bounds.height, c);
|
||||
}
|
||||
// process a square area before the track
|
||||
RegionPainter<Object> leading = UIUtil.getClientProperty(c, LEADING_AREA);
|
||||
if (leading == null) {
|
||||
myLeadingBounds.setSize(0, 0);
|
||||
}
|
||||
else if (alignment == Alignment.LEFT || alignment == Alignment.RIGHT) {
|
||||
int size = bounds.width;
|
||||
myLeadingBounds.setBounds(bounds.x, bounds.y, size, size);
|
||||
leading.paint((Graphics2D)g, bounds.x, bounds.y, size, size, null);
|
||||
bounds.height -= size;
|
||||
bounds.y += size;
|
||||
}
|
||||
else {
|
||||
int size = bounds.height;
|
||||
myLeadingBounds.setBounds(bounds.x, bounds.y, size, size);
|
||||
leading.paint((Graphics2D)g, bounds.x, bounds.y, size, size, null);
|
||||
bounds.width -= size;
|
||||
bounds.x += size;
|
||||
}
|
||||
// process additional drawing on the track
|
||||
myTrackBounds.setBounds(bounds);
|
||||
RegionPainter<Object> track = UIUtil.getClientProperty(c, JBScrollBar.TRACK);
|
||||
if (track != null && myTrackBounds.width > 0 && myTrackBounds.height > 0) {
|
||||
track.paint((Graphics2D)g, myTrackBounds.x, myTrackBounds.y, myTrackBounds.width, myTrackBounds.height, null);
|
||||
}
|
||||
// process drawing the thumb
|
||||
updateThumbBounds();
|
||||
if (myThumbBounds.width > 0 && myThumbBounds.height > 0) {
|
||||
paintThumb((Graphics2D)g, myThumbBounds.x, myThumbBounds.y, myThumbBounds.width, myThumbBounds.height, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateThumbBounds() {
|
||||
int min = myScrollBar.getMinimum();
|
||||
int max = myScrollBar.getMaximum();
|
||||
int range = max - min;
|
||||
if (range <= 0) {
|
||||
myThumbBounds.setBounds(0, 0, 0, 0);
|
||||
}
|
||||
else if (VERTICAL == myScrollBar.getOrientation()) {
|
||||
int extent = myScrollBar.getVisibleAmount();
|
||||
int height = Math.max(myTrackBounds.height * extent / range, 2 * getThickness());
|
||||
if (myTrackBounds.height <= height) {
|
||||
myThumbBounds.setBounds(0, 0, 0, 0);
|
||||
}
|
||||
else {
|
||||
int value = getValue();
|
||||
int maxY = myTrackBounds.y + myTrackBounds.height - height;
|
||||
int y = (value < max - extent) ? (myTrackBounds.height - height) * (value - min) / (range - extent) : maxY;
|
||||
myThumbBounds.setBounds(myTrackBounds.x, adjust(y, myTrackBounds.y, maxY), myTrackBounds.width, height);
|
||||
}
|
||||
}
|
||||
else {
|
||||
int extent = myScrollBar.getVisibleAmount();
|
||||
int width = Math.max(myTrackBounds.width * extent / range, 2 * getThickness());
|
||||
if (myTrackBounds.width <= width) {
|
||||
myThumbBounds.setBounds(0, 0, 0, 0);
|
||||
}
|
||||
else {
|
||||
int value = getValue();
|
||||
int maxX = myTrackBounds.x + myTrackBounds.width - width;
|
||||
int x = (value < max - extent) ? (myTrackBounds.width - width) * (value - min) / (range - extent) : maxX;
|
||||
myThumbBounds.setBounds(adjust(x, myTrackBounds.x, maxX), myTrackBounds.y, width, myTrackBounds.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getValue() {
|
||||
return isValueCached ? myCachedValue : myScrollBar.getValue();
|
||||
}
|
||||
|
||||
private static int adjust(int value, int min, int max) {
|
||||
return value < min ? min : value > max ? max : value;
|
||||
}
|
||||
|
||||
private final class Listener extends MouseAdapter implements ActionListener, FocusListener, ChangeListener, PropertyChangeListener {
|
||||
private int myOffset;
|
||||
private int myMouseX, myMouseY;
|
||||
private boolean isReversed;
|
||||
private boolean isDragging;
|
||||
private boolean isOverTrack;
|
||||
private boolean isOverThumb;
|
||||
|
||||
private void updateMouse(int x, int y) {
|
||||
if (myTrackBounds.contains(x, y)) {
|
||||
if (!isOverTrack) onTrackHover(isOverTrack = true);
|
||||
boolean hover = myThumbBounds.contains(x, y);
|
||||
if (isOverThumb != hover) onThumbHover(isOverThumb = hover);
|
||||
}
|
||||
else {
|
||||
updateMouseExit();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMouseExit() {
|
||||
if (isOverThumb) onThumbHover(isOverThumb = false);
|
||||
if (isOverTrack) onTrackHover(isOverTrack = false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent event) {
|
||||
if (myScrollBar == null || !myScrollBar.isEnabled()) return;
|
||||
if (SwingUtilities.isRightMouseButton(event)) return;
|
||||
|
||||
isValueCached = true;
|
||||
myCachedValue = myScrollBar.getValue();
|
||||
myScrollBar.setValueIsAdjusting(true);
|
||||
|
||||
myMouseX = event.getX();
|
||||
myMouseY = event.getY();
|
||||
|
||||
boolean vertical = VERTICAL == myScrollBar.getOrientation();
|
||||
if (myThumbBounds.contains(myMouseX, myMouseY)) {
|
||||
// pressed on the thumb
|
||||
myOffset = vertical ? (myMouseY - myThumbBounds.y) : (myMouseX - myThumbBounds.x);
|
||||
isDragging = true;
|
||||
}
|
||||
else if (isTrackVisible && myTrackBounds.contains(myMouseX, myMouseY)) {
|
||||
// pressed on the track
|
||||
if (SwingUtilities.isMiddleMouseButton(event)) {
|
||||
myOffset = (vertical ? myThumbBounds.height : myThumbBounds.width) / 2;
|
||||
isDragging = true;
|
||||
setValueFrom(event);
|
||||
}
|
||||
else {
|
||||
myScrollTimer.stop();
|
||||
isDragging = false;
|
||||
if (VERTICAL == myScrollBar.getOrientation()) {
|
||||
int y = myThumbBounds.isEmpty() ? myScrollBar.getHeight() / 2 : myThumbBounds.y;
|
||||
isReversed = myMouseY < y;
|
||||
}
|
||||
else {
|
||||
int x = myThumbBounds.isEmpty() ? myScrollBar.getWidth() / 2 : myThumbBounds.x;
|
||||
isReversed = myMouseX < x;
|
||||
if (!myScrollBar.getComponentOrientation().isLeftToRight()) {
|
||||
isReversed = !isReversed;
|
||||
}
|
||||
}
|
||||
scroll(isReversed);
|
||||
startScrollTimerIfNecessary();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent event) {
|
||||
if (isDragging) updateMouse(event.getX(), event.getY());
|
||||
if (myScrollBar == null || !myScrollBar.isEnabled()) return;
|
||||
if (SwingUtilities.isRightMouseButton(event)) return;
|
||||
isDragging = false;
|
||||
myOffset = 0;
|
||||
myScrollTimer.stop();
|
||||
isValueCached = true;
|
||||
myCachedValue = myScrollBar.getValue();
|
||||
myScrollBar.setValueIsAdjusting(false);
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent event) {
|
||||
if (myScrollBar == null || !myScrollBar.isEnabled()) return;
|
||||
if (myThumbBounds.isEmpty() || SwingUtilities.isRightMouseButton(event)) return;
|
||||
if (isDragging) {
|
||||
setValueFrom(event);
|
||||
}
|
||||
else {
|
||||
myMouseX = event.getX();
|
||||
myMouseY = event.getY();
|
||||
updateMouse(myMouseX, myMouseY);
|
||||
startScrollTimerIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent event) {
|
||||
if (myScrollBar == null || !myScrollBar.isEnabled()) return;
|
||||
if (!isDragging) updateMouse(event.getX(), event.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent event) {
|
||||
if (myScrollBar == null || !myScrollBar.isEnabled()) return;
|
||||
if (!isDragging) updateMouseExit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
if (myScrollBar == null) {
|
||||
myScrollTimer.stop();
|
||||
}
|
||||
else {
|
||||
scroll(isReversed);
|
||||
if (!myThumbBounds.isEmpty()) {
|
||||
if (isReversed ? !isMouseBeforeThumb() : !isMouseAfterThumb()) {
|
||||
myScrollTimer.stop();
|
||||
}
|
||||
}
|
||||
int value = myScrollBar.getValue();
|
||||
if (isReversed ? value <= myScrollBar.getMinimum() : value >= myScrollBar.getMaximum() - myScrollBar.getVisibleAmount()) {
|
||||
myScrollTimer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusGained(FocusEvent event) {
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusLost(FocusEvent event) {
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent event) {
|
||||
updateThumbBounds();
|
||||
// TODO: update mouse
|
||||
isValueCached = false;
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent event) {
|
||||
String name = event.getPropertyName();
|
||||
if ("model" == name) {
|
||||
BoundedRangeModel oldModel = (BoundedRangeModel)event.getOldValue();
|
||||
BoundedRangeModel newModel = (BoundedRangeModel)event.getNewValue();
|
||||
oldModel.removeChangeListener(this);
|
||||
newModel.addChangeListener(this);
|
||||
}
|
||||
if ("model" == name || "orientation" == name || "componentOrientation" == name) {
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void setValueFrom(MouseEvent event) {
|
||||
int x = event.getX();
|
||||
int y = event.getY();
|
||||
|
||||
int thumbMin, thumbMax, thumbPos;
|
||||
if (VERTICAL == myScrollBar.getOrientation()) {
|
||||
thumbMin = myTrackBounds.y;
|
||||
thumbMax = myTrackBounds.y + myTrackBounds.height - myThumbBounds.height;
|
||||
thumbPos = Math.min(thumbMax, Math.max(thumbMin, (y - myOffset)));
|
||||
if (myThumbBounds.y != thumbPos) {
|
||||
int minY = Math.min(myThumbBounds.y, thumbPos);
|
||||
int maxY = Math.max(myThumbBounds.y, thumbPos) + myThumbBounds.height;
|
||||
myThumbBounds.y = thumbPos;
|
||||
repaint(myThumbBounds.x, minY, myThumbBounds.width, maxY - minY);
|
||||
}
|
||||
}
|
||||
else {
|
||||
thumbMin = myTrackBounds.x;
|
||||
thumbMax = myTrackBounds.x + myTrackBounds.width - myThumbBounds.width;
|
||||
thumbPos = Math.min(thumbMax, Math.max(thumbMin, (x - myOffset)));
|
||||
if (myThumbBounds.x != thumbPos) {
|
||||
int minX = Math.min(myThumbBounds.x, thumbPos);
|
||||
int maxX = Math.max(myThumbBounds.x, thumbPos) + myThumbBounds.width;
|
||||
myThumbBounds.x = thumbPos;
|
||||
repaint(minX, myThumbBounds.y, maxX - minX, myThumbBounds.height);
|
||||
}
|
||||
}
|
||||
int valueMin = myScrollBar.getMinimum();
|
||||
int valueMax = myScrollBar.getMaximum() - myScrollBar.getVisibleAmount();
|
||||
// If the thumb has reached the end of the scrollbar, then just set the value to its maximum.
|
||||
// Otherwise compute the value as accurately as possible.
|
||||
boolean isDefaultOrientation = VERTICAL == myScrollBar.getOrientation() || myScrollBar.getComponentOrientation().isLeftToRight();
|
||||
if (thumbPos == thumbMax) {
|
||||
myScrollBar.setValue(isDefaultOrientation ? valueMax : valueMin);
|
||||
}
|
||||
else {
|
||||
int valueRange = valueMax - valueMin;
|
||||
int thumbRange = thumbMax - thumbMin;
|
||||
int thumbValue = isDefaultOrientation
|
||||
? thumbPos - thumbMin
|
||||
: thumbMax - thumbPos;
|
||||
isValueCached = true;
|
||||
myCachedValue = valueMin + valueRange * thumbValue / thumbRange;
|
||||
myScrollBar.setValue(myCachedValue);
|
||||
}
|
||||
if (!isDragging) updateMouse(x, y);
|
||||
}
|
||||
|
||||
private void startScrollTimerIfNecessary() {
|
||||
if (!myScrollTimer.isRunning()) {
|
||||
if (isReversed ? isMouseBeforeThumb() : isMouseAfterThumb()) {
|
||||
myScrollTimer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMouseBeforeThumb() {
|
||||
return VERTICAL == myScrollBar.getOrientation()
|
||||
? isMouseOnTop()
|
||||
: myScrollBar.getComponentOrientation().isLeftToRight()
|
||||
? isMouseOnLeft()
|
||||
: isMouseOnRight();
|
||||
}
|
||||
|
||||
private boolean isMouseAfterThumb() {
|
||||
return VERTICAL == myScrollBar.getOrientation()
|
||||
? isMouseOnBottom()
|
||||
: myScrollBar.getComponentOrientation().isLeftToRight()
|
||||
? isMouseOnRight()
|
||||
: isMouseOnLeft();
|
||||
}
|
||||
|
||||
private boolean isMouseOnTop() {
|
||||
return myMouseY < myThumbBounds.y;
|
||||
}
|
||||
|
||||
private boolean isMouseOnLeft() {
|
||||
return myMouseX < myThumbBounds.x;
|
||||
}
|
||||
|
||||
private boolean isMouseOnRight() {
|
||||
return myMouseX > myThumbBounds.x + myThumbBounds.width;
|
||||
}
|
||||
|
||||
private boolean isMouseOnBottom() {
|
||||
return myMouseY > myThumbBounds.y + myThumbBounds.height;
|
||||
}
|
||||
|
||||
private void scroll(boolean reversed) {
|
||||
int delta = myScrollBar.getBlockIncrement(reversed ? -1 : 1);
|
||||
if (reversed) delta = -delta;
|
||||
|
||||
int oldValue = myScrollBar.getValue();
|
||||
int newValue = oldValue + delta;
|
||||
|
||||
if (delta > 0 && newValue < oldValue) {
|
||||
newValue = myScrollBar.getMaximum();
|
||||
}
|
||||
else if (delta < 0 && newValue > oldValue) {
|
||||
newValue = myScrollBar.getMinimum();
|
||||
}
|
||||
if (oldValue != newValue) {
|
||||
myScrollBar.setValue(newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.components;
|
||||
|
||||
import com.intellij.ui.Gray;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.ui.components.JBScrollBar.Alignment;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Composite;
|
||||
import java.awt.Graphics2D;
|
||||
import javax.swing.JComponent;
|
||||
|
||||
/**
|
||||
* @author Sergey.Malenkov
|
||||
*/
|
||||
final class DefaultScrollBarUI extends AbstractScrollBarUI {
|
||||
private static final JBColor THUMB_BACKGROUND = new JBColor(0x808080, 0x808080);
|
||||
private static final JBColor THUMB_FOREGROUND = new JBColor(0x6E6E6E, 0x949494);
|
||||
|
||||
private float myTrackValue;
|
||||
private float myThumbValue;
|
||||
|
||||
@Override
|
||||
int getThickness() {
|
||||
return scale(14);
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMinimalThickness() {
|
||||
return scale(10);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onTrackHover(boolean hover) {
|
||||
if (hover) {
|
||||
myTrackAnimator.startForward();
|
||||
}
|
||||
else {
|
||||
myTrackAnimator.startBackward();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void onThumbHover(boolean hover) {
|
||||
if (hover) {
|
||||
myThumbAnimator.startForward();
|
||||
}
|
||||
else {
|
||||
myThumbAnimator.startBackward();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void paintTrack(Graphics2D g, int x, int y, int width, int height, JComponent c) {
|
||||
Composite old = g.getComposite();
|
||||
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .1f * myTrackValue));
|
||||
g.setColor(Gray.x80);
|
||||
g.fillRect(x, y, width, height);
|
||||
g.setComposite(old);
|
||||
}
|
||||
|
||||
@Override
|
||||
void paintThumb(Graphics2D g, int x, int y, int width, int height, JComponent c) {
|
||||
Composite old = g.getComposite();
|
||||
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .4f + .1f * myThumbValue));
|
||||
if (!c.isOpaque()) {
|
||||
Alignment alignment = Alignment.get(c);
|
||||
if (alignment == Alignment.LEFT || alignment == Alignment.RIGHT) {
|
||||
int offset = width - getMinimalThickness();
|
||||
if (offset > 0) {
|
||||
offset *= 1 - myThumbValue;
|
||||
width -= offset;
|
||||
if (alignment == Alignment.RIGHT) x += offset;
|
||||
}
|
||||
}
|
||||
else {
|
||||
int offset = height - getMinimalThickness();
|
||||
if (offset > 0) {
|
||||
offset *= 1 - myThumbValue;
|
||||
height -= offset;
|
||||
if (alignment == Alignment.BOTTOM) y += offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
g.setColor(THUMB_BACKGROUND);
|
||||
g.fillRect(x + 1, y + 1, width - 2, height - 2);
|
||||
g.setColor(THUMB_FOREGROUND);
|
||||
g.drawRect(x, y, width - 1, height - 1);
|
||||
g.setComposite(old);
|
||||
}
|
||||
|
||||
private TwoWayAnimator myTrackAnimator = new TwoWayAnimator("ScrollBarTrack", 6, 300, 300, 1000) {
|
||||
@Override
|
||||
void onFrame(int frame, int maxFrame) {
|
||||
myTrackValue = (float)frame / maxFrame;
|
||||
setTrackVisible(frame > 0);
|
||||
repaint();
|
||||
}
|
||||
};
|
||||
|
||||
private TwoWayAnimator myThumbAnimator = new TwoWayAnimator("ScrollBarThumb", 5, 125, 300, 1000) {
|
||||
@Override
|
||||
void onFrame(int frame, int maxFrame) {
|
||||
myThumbValue = (float)frame / maxFrame;
|
||||
repaint();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -15,15 +15,20 @@
|
||||
*/
|
||||
package com.intellij.ui.components;
|
||||
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.wm.IdeGlassPane;
|
||||
import com.intellij.ui.IdeBorderFactory;
|
||||
import com.intellij.ui.components.JBScrollBar.Alignment;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ReflectionUtil;
|
||||
import com.intellij.util.ui.ButtonlessScrollBarUI;
|
||||
import com.intellij.util.ui.JBInsets;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.LineBorder;
|
||||
import javax.swing.plaf.ScrollBarUI;
|
||||
import javax.swing.plaf.ScrollPaneUI;
|
||||
@@ -67,7 +72,7 @@ public class JBScrollPane extends JScrollPane {
|
||||
Container vp = c.getParent();
|
||||
if (vp instanceof JViewport) c = vp;
|
||||
}
|
||||
|
||||
|
||||
c = c.getParent();
|
||||
if (!(c instanceof JScrollPane)) return null;
|
||||
|
||||
@@ -77,10 +82,10 @@ public class JBScrollPane extends JScrollPane {
|
||||
private void init() {
|
||||
init(true);
|
||||
}
|
||||
|
||||
|
||||
private void init(boolean setupCorners) {
|
||||
setLayout(new ScrollPaneLayout());
|
||||
|
||||
setLayout(Registry.is("ide.scroll.new.layout") ? new Layout() : new ScrollPaneLayout());
|
||||
|
||||
if (setupCorners) {
|
||||
setupCorners();
|
||||
}
|
||||
@@ -102,9 +107,21 @@ public class JBScrollPane extends JScrollPane {
|
||||
|
||||
@Override
|
||||
public boolean isOptimizedDrawingEnabled() {
|
||||
if (getLayout() instanceof Layout) {
|
||||
return isOptimizedDrawingEnabledFor(getVerticalScrollBar()) &&
|
||||
isOptimizedDrawingEnabledFor(getHorizontalScrollBar());
|
||||
}
|
||||
return !myHasOverlayScrollbars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code false} for visible translucent scroll bars, or {@code true} otherwise.
|
||||
* It is needed to repaint translucent scroll bars on viewport repainting.
|
||||
*/
|
||||
private static boolean isOptimizedDrawingEnabledFor(JScrollBar bar) {
|
||||
return bar == null || bar.isOpaque() || !bar.isVisible();
|
||||
}
|
||||
|
||||
private void updateViewportBorder() {
|
||||
setViewportBorder(new ViewportBorder(myViewportBorderWidth >= 0 ? myViewportBorderWidth : 1));
|
||||
}
|
||||
@@ -138,8 +155,10 @@ public class JBScrollPane extends JScrollPane {
|
||||
// Now we let JScrollPane layout everything as necessary
|
||||
super.layout();
|
||||
|
||||
if (layout instanceof Layout) return;
|
||||
|
||||
if (scrollLayout != null) {
|
||||
// Now it's time to jump in and expand the viewport so it fits the whole area
|
||||
// Now it's time to jump in and expand the viewport so it fits the whole area
|
||||
// (taking into consideration corners, headers and other stuff).
|
||||
myHasOverlayScrollbars = relayoutScrollbars(
|
||||
this, scrollLayout,
|
||||
@@ -154,7 +173,7 @@ public class JBScrollPane extends JScrollPane {
|
||||
private boolean relayoutScrollbars(@NotNull JComponent container, @NotNull ScrollPaneLayout layout, boolean forceRelayout) {
|
||||
JViewport viewport = layout.getViewport();
|
||||
if (viewport == null) return false;
|
||||
|
||||
|
||||
JScrollBar vsb = layout.getVerticalScrollBar();
|
||||
JScrollBar hsb = layout.getHorizontalScrollBar();
|
||||
JViewport colHead = layout.getColumnHeader();
|
||||
@@ -225,7 +244,7 @@ public class JBScrollPane extends JScrollPane {
|
||||
|
||||
protected boolean isOverlaidScrollbar(@Nullable JScrollBar scrollbar) {
|
||||
if (!ButtonlessScrollBarUI.isMacOverlayScrollbarSupported()) return false;
|
||||
|
||||
|
||||
ScrollBarUI vsbUI = scrollbar == null ? null : scrollbar.getUI();
|
||||
return vsbUI instanceof ButtonlessScrollBarUI && !((ButtonlessScrollBarUI)vsbUI).alwaysShowTrack();
|
||||
}
|
||||
@@ -242,7 +261,9 @@ public class JBScrollPane extends JScrollPane {
|
||||
|
||||
@Override
|
||||
public void updateUI() {
|
||||
setUI(ButtonlessScrollBarUI.createNormal());
|
||||
setUI(!SystemInfo.isMac && Registry.is("ide.scroll.new.layout")
|
||||
? new DefaultScrollBarUI()
|
||||
: ButtonlessScrollBarUI.createNormal());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -327,4 +348,303 @@ public class JBScrollPane extends JScrollPane {
|
||||
lineColor = view.getBackground();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* These client properties modify a scroll pane layout.
|
||||
* Use the class object as a property key.
|
||||
*
|
||||
* @see #putClientProperty(Object, Object)
|
||||
*/
|
||||
public enum Flip {
|
||||
NONE, VERTICAL, HORIZONTAL, BOTH
|
||||
}
|
||||
|
||||
/**
|
||||
* ScrollPaneLayout implementation that supports
|
||||
* ScrollBar flipping and non-opaque ScrollBars.
|
||||
*/
|
||||
private static class Layout extends ScrollPaneLayout {
|
||||
private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
|
||||
|
||||
@Override
|
||||
public void layoutContainer(Container parent) {
|
||||
JScrollPane pane = (JScrollPane)parent;
|
||||
// Calculate inner bounds of the scroll pane
|
||||
Rectangle bounds = new Rectangle(pane.getWidth(), pane.getHeight());
|
||||
JBInsets.removeFrom(bounds, pane.getInsets());
|
||||
// Determine positions of scroll bars on the scroll pane
|
||||
Object property = pane.getClientProperty(Flip.class);
|
||||
Flip flip = property instanceof Flip ? (Flip)property : Flip.NONE;
|
||||
boolean hsbOnTop = flip == Flip.BOTH || flip == Flip.VERTICAL;
|
||||
boolean vsbOnLeft = pane.getComponentOrientation().isLeftToRight()
|
||||
? flip == Flip.BOTH || flip == Flip.HORIZONTAL
|
||||
: flip == Flip.NONE || flip == Flip.VERTICAL;
|
||||
// If there's a visible row header remove the space it needs.
|
||||
// The row header is treated as if it were fixed width, arbitrary height.
|
||||
Rectangle rowHeadBounds = new Rectangle(bounds.x, 0, 0, 0);
|
||||
if (rowHead != null && rowHead.isVisible()) {
|
||||
rowHeadBounds.width = min(bounds.width, rowHead.getPreferredSize().width);
|
||||
bounds.width -= rowHeadBounds.width;
|
||||
if (vsbOnLeft) {
|
||||
rowHeadBounds.x += bounds.width;
|
||||
}
|
||||
else {
|
||||
bounds.x += rowHeadBounds.width;
|
||||
}
|
||||
}
|
||||
// If there's a visible column header remove the space it needs.
|
||||
// The column header is treated as if it were fixed height, arbitrary width.
|
||||
Rectangle colHeadBounds = new Rectangle(0, bounds.y, 0, 0);
|
||||
if (colHead != null && colHead.isVisible()) {
|
||||
colHeadBounds.height = min(bounds.height, colHead.getPreferredSize().height);
|
||||
bounds.height -= colHeadBounds.height;
|
||||
if (hsbOnTop) {
|
||||
colHeadBounds.y += bounds.height;
|
||||
}
|
||||
else {
|
||||
bounds.y += colHeadBounds.height;
|
||||
}
|
||||
}
|
||||
// If there's a JScrollPane.viewportBorder, remove the space it occupies
|
||||
Border border = pane.getViewportBorder();
|
||||
Insets insets = border == null ? null : border.getBorderInsets(parent);
|
||||
JBInsets.removeFrom(bounds, insets);
|
||||
if (insets == null) insets = EMPTY_INSETS;
|
||||
// At this point:
|
||||
// colHeadBounds is correct except for its width and x
|
||||
// rowHeadBounds is correct except for its height and y
|
||||
// bounds - the space available for the viewport and scroll bars
|
||||
// Once we're through computing the dimensions of these three parts
|
||||
// we can go back and set the bounds for the corners and the dimensions of
|
||||
// colHeadBounds.x, colHeadBounds.width, rowHeadBounds.y, rowHeadBounds.height.
|
||||
boolean isEmpty = bounds.width < 0 || bounds.height < 0;
|
||||
Component view = viewport == null ? null : viewport.getView();
|
||||
Dimension viewPreferredSize = view == null ? new Dimension() : view.getPreferredSize();
|
||||
Dimension viewportExtentSize = viewport == null ? new Dimension() : viewport.toViewCoordinates(bounds.getSize());
|
||||
// If the view is tracking the viewports width we don't bother with a horizontal scrollbar.
|
||||
// If the view is tracking the viewports height we don't bother with a vertical scrollbar.
|
||||
Scrollable scrollable = null;
|
||||
boolean viewTracksViewportWidth = false;
|
||||
boolean viewTracksViewportHeight = false;
|
||||
// Don't bother checking the Scrollable methods if there is no room for the viewport,
|
||||
// we aren't going to show any scroll bars in this case anyway.
|
||||
if (!isEmpty && view instanceof Scrollable) {
|
||||
scrollable = (Scrollable)view;
|
||||
viewTracksViewportWidth = scrollable.getScrollableTracksViewportWidth();
|
||||
viewTracksViewportHeight = scrollable.getScrollableTracksViewportHeight();
|
||||
}
|
||||
// If there's a vertical scroll bar and we need one, allocate space for it.
|
||||
// A vertical scroll bar is considered to be fixed width, arbitrary height.
|
||||
boolean vsbOpaque = false;
|
||||
boolean vsbNeeded = false;
|
||||
int vsbPolicy = pane.getVerticalScrollBarPolicy();
|
||||
if (!isEmpty && vsbPolicy != VERTICAL_SCROLLBAR_NEVER) {
|
||||
vsbNeeded = vsbPolicy == VERTICAL_SCROLLBAR_ALWAYS
|
||||
|| !viewTracksViewportHeight && viewPreferredSize.height > viewportExtentSize.height;
|
||||
}
|
||||
Rectangle vsbBounds = new Rectangle(0, bounds.y - insets.top, 0, 0);
|
||||
if (vsb != null) {
|
||||
vsbOpaque = vsb.isOpaque();
|
||||
if (vsbNeeded) {
|
||||
adjustForVSB(bounds, insets, vsbBounds, vsbOpaque, vsbOnLeft);
|
||||
if (vsbOpaque && viewport != null) {
|
||||
viewportExtentSize = viewport.toViewCoordinates(bounds.getSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there's a horizontal scroll bar and we need one, allocate space for it.
|
||||
// A horizontal scroll bar is considered to be fixed height, arbitrary width.
|
||||
boolean hsbOpaque = false;
|
||||
boolean hsbNeeded = false;
|
||||
int hsbPolicy = pane.getHorizontalScrollBarPolicy();
|
||||
if (!isEmpty && hsbPolicy != HORIZONTAL_SCROLLBAR_NEVER) {
|
||||
hsbNeeded = hsbPolicy == HORIZONTAL_SCROLLBAR_ALWAYS
|
||||
|| !viewTracksViewportWidth && viewPreferredSize.width > viewportExtentSize.width;
|
||||
}
|
||||
Rectangle hsbBounds = new Rectangle(bounds.x - insets.left, 0, 0, 0);
|
||||
if (hsb != null) {
|
||||
hsbOpaque = hsb.isOpaque();
|
||||
if (hsbNeeded) {
|
||||
adjustForHSB(bounds, insets, hsbBounds, hsbOpaque, hsbOnTop);
|
||||
if (hsbOpaque && viewport != null) {
|
||||
// If we added the horizontal scrollbar and reduced the vertical space
|
||||
// we may have to add the vertical scrollbar, if that hasn't been done so already.
|
||||
if (vsb != null && !vsbNeeded && vsbPolicy != VERTICAL_SCROLLBAR_NEVER) {
|
||||
viewportExtentSize = viewport.toViewCoordinates(bounds.getSize());
|
||||
vsbNeeded = viewPreferredSize.height > viewportExtentSize.height;
|
||||
if (vsbNeeded) adjustForVSB(bounds, insets, vsbBounds, vsbOpaque, vsbOnLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set the size of the viewport first, and then recheck the Scrollable methods.
|
||||
// Some components base their return values for the Scrollable methods on the size of the viewport,
|
||||
// so that if we don't ask after resetting the bounds we may have gotten the wrong answer.
|
||||
if (viewport != null) {
|
||||
viewport.setBounds(bounds);
|
||||
pane.setComponentZOrder(viewport, pane.getComponentCount() - 1);
|
||||
if (scrollable != null && hsbOpaque && vsbOpaque) {
|
||||
viewTracksViewportWidth = scrollable.getScrollableTracksViewportWidth();
|
||||
viewTracksViewportHeight = scrollable.getScrollableTracksViewportHeight();
|
||||
viewportExtentSize = viewport.toViewCoordinates(bounds.getSize());
|
||||
|
||||
boolean vsbNeededOld = vsbNeeded;
|
||||
if (vsb != null && vsbPolicy == VERTICAL_SCROLLBAR_AS_NEEDED) {
|
||||
boolean vsbNeededNew = !viewTracksViewportHeight && viewPreferredSize.height > viewportExtentSize.height;
|
||||
if (vsbNeeded != vsbNeededNew) {
|
||||
vsbNeeded = vsbNeededNew;
|
||||
if (vsbNeeded) {
|
||||
adjustForVSB(bounds, insets, vsbBounds, vsbOpaque, vsbOnLeft);
|
||||
}
|
||||
else if (vsbOpaque) {
|
||||
bounds.width += vsbBounds.width;
|
||||
}
|
||||
if (vsbOpaque) viewportExtentSize = viewport.toViewCoordinates(bounds.getSize());
|
||||
}
|
||||
}
|
||||
boolean hsbNeededOld = hsbNeeded;
|
||||
if (hsb != null && hsbPolicy == HORIZONTAL_SCROLLBAR_AS_NEEDED) {
|
||||
boolean hsbNeededNew = !viewTracksViewportWidth && viewPreferredSize.width > viewportExtentSize.width;
|
||||
if (hsbNeeded != hsbNeededNew) {
|
||||
hsbNeeded = hsbNeededNew;
|
||||
if (hsbNeeded) {
|
||||
adjustForHSB(bounds, insets, hsbBounds, hsbOpaque, hsbOnTop);
|
||||
}
|
||||
else if (hsbOpaque) {
|
||||
bounds.height += hsbBounds.height;
|
||||
}
|
||||
if (hsbOpaque && vsb != null && !vsbNeeded && vsbPolicy != VERTICAL_SCROLLBAR_NEVER) {
|
||||
viewportExtentSize = viewport.toViewCoordinates(bounds.getSize());
|
||||
vsbNeeded = viewPreferredSize.height > viewportExtentSize.height;
|
||||
if (vsbNeeded) adjustForVSB(bounds, insets, vsbBounds, vsbOpaque, vsbOnLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hsbNeededOld != hsbNeeded || vsbNeededOld != vsbNeeded) {
|
||||
viewport.setBounds(bounds);
|
||||
// You could argue that we should recheck the Scrollable methods again until they stop changing,
|
||||
// but they might never stop changing, so we stop here and don't do any additional checks.
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set the bounds of the row header.
|
||||
rowHeadBounds.y = bounds.y - insets.top;
|
||||
rowHeadBounds.height = bounds.height + insets.top + insets.bottom;
|
||||
if (rowHead != null) {
|
||||
rowHead.setBounds(rowHeadBounds);
|
||||
rowHead.putClientProperty(Alignment.class, vsbOnLeft ? Alignment.RIGHT : Alignment.LEFT);
|
||||
}
|
||||
// Set the bounds of the column header.
|
||||
colHeadBounds.x = bounds.x - insets.left;
|
||||
colHeadBounds.width = bounds.width + insets.left + insets.right;
|
||||
if (colHead != null) {
|
||||
colHead.setBounds(colHeadBounds);
|
||||
colHead.putClientProperty(Alignment.class, hsbOnTop ? Alignment.BOTTOM : Alignment.TOP);
|
||||
}
|
||||
// Set the bounds of the vertical scroll bar.
|
||||
vsbBounds.y = bounds.y - insets.top;
|
||||
vsbBounds.height = bounds.height + insets.top + insets.bottom;
|
||||
if (vsb != null) {
|
||||
vsb.setVisible(vsbNeeded);
|
||||
if (vsbNeeded) {
|
||||
if (vsbOpaque && colHead != null && UIManager.getBoolean("ScrollPane.fillUpperCorner")) {
|
||||
if ((vsbOnLeft ? upperLeft : upperRight) == null) {
|
||||
// This is used primarily for GTK L&F, which needs to extend
|
||||
// the vertical scrollbar to fill the upper corner near the column header.
|
||||
// Note that we skip this step (and use the default behavior)
|
||||
// if the user has set a custom corner component.
|
||||
if (!hsbOnTop) vsbBounds.y -= colHeadBounds.height;
|
||||
vsbBounds.height += colHeadBounds.height;
|
||||
}
|
||||
}
|
||||
vsb.setBounds(vsbBounds);
|
||||
vsb.putClientProperty(Alignment.class, vsbOnLeft ? Alignment.LEFT : Alignment.RIGHT);
|
||||
pane.setComponentZOrder(vsb, 0);
|
||||
}
|
||||
// Modify the bounds of the translucent scroll bar.
|
||||
if (!vsbOpaque) {
|
||||
if (!vsbOnLeft) vsbBounds.x += vsbBounds.width;
|
||||
vsbBounds.width = 0;
|
||||
}
|
||||
}
|
||||
// Set the bounds of the horizontal scroll bar.
|
||||
hsbBounds.x = bounds.x - insets.left;
|
||||
hsbBounds.width = bounds.width + insets.left + insets.right;
|
||||
if (hsb != null) {
|
||||
hsb.setVisible(hsbNeeded);
|
||||
if (hsbNeeded) {
|
||||
if (hsbOpaque && rowHead != null && UIManager.getBoolean("ScrollPane.fillLowerCorner")) {
|
||||
if ((vsbOnLeft ? lowerRight : lowerLeft) == null) {
|
||||
// This is used primarily for GTK L&F, which needs to extend
|
||||
// the horizontal scrollbar to fill the lower corner near the row header.
|
||||
// Note that we skip this step (and use the default behavior)
|
||||
// if the user has set a custom corner component.
|
||||
if (!vsbOnLeft) hsbBounds.x -= rowHeadBounds.width;
|
||||
hsbBounds.width += rowHeadBounds.width;
|
||||
}
|
||||
}
|
||||
hsb.setBounds(hsbBounds);
|
||||
hsb.putClientProperty(Alignment.class, hsbOnTop ? Alignment.TOP : Alignment.BOTTOM);
|
||||
}
|
||||
// Modify the bounds of the translucent scroll bar.
|
||||
if (!hsbOpaque) {
|
||||
if (!hsbOnTop) hsbBounds.y += hsbBounds.height;
|
||||
hsbBounds.height = 0;
|
||||
}
|
||||
}
|
||||
// Set the bounds of the corners.
|
||||
if (lowerLeft != null) {
|
||||
lowerLeft.setBounds(vsbOnLeft ? vsbBounds.x : rowHeadBounds.x,
|
||||
hsbOnTop ? colHeadBounds.y : hsbBounds.y,
|
||||
vsbOnLeft ? vsbBounds.width : rowHeadBounds.width,
|
||||
hsbOnTop ? colHeadBounds.height : hsbBounds.height);
|
||||
}
|
||||
if (lowerRight != null) {
|
||||
lowerRight.setBounds(vsbOnLeft ? rowHeadBounds.x : vsbBounds.x,
|
||||
hsbOnTop ? colHeadBounds.y : hsbBounds.y,
|
||||
vsbOnLeft ? rowHeadBounds.width : vsbBounds.width,
|
||||
hsbOnTop ? colHeadBounds.height : hsbBounds.height);
|
||||
}
|
||||
if (upperLeft != null) {
|
||||
upperLeft.setBounds(vsbOnLeft ? vsbBounds.x : rowHeadBounds.x,
|
||||
hsbOnTop ? hsbBounds.y : colHeadBounds.y,
|
||||
vsbOnLeft ? vsbBounds.width : rowHeadBounds.width,
|
||||
hsbOnTop ? hsbBounds.height : colHeadBounds.height);
|
||||
}
|
||||
if (upperRight != null) {
|
||||
upperRight.setBounds(vsbOnLeft ? rowHeadBounds.x : vsbBounds.x,
|
||||
hsbOnTop ? hsbBounds.y : colHeadBounds.y,
|
||||
vsbOnLeft ? rowHeadBounds.width : vsbBounds.width,
|
||||
hsbOnTop ? hsbBounds.height : colHeadBounds.height);
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustForVSB(Rectangle bounds, Insets insets, Rectangle vsbBounds, boolean vsbOpaque, boolean vsbOnLeft) {
|
||||
vsbBounds.width = min(bounds.width, vsb.getPreferredSize().width);
|
||||
if (vsbOnLeft) {
|
||||
vsbBounds.x = bounds.x - insets.left/* + vsbBounds.width*/;
|
||||
if (vsbOpaque) bounds.x += vsbBounds.width;
|
||||
}
|
||||
else {
|
||||
vsbBounds.x = bounds.x + bounds.width + insets.right - vsbBounds.width;
|
||||
}
|
||||
if (vsbOpaque) bounds.width -= vsbBounds.width;
|
||||
}
|
||||
|
||||
private void adjustForHSB(Rectangle bounds, Insets insets, Rectangle hsbBounds, boolean hsbOpaque, boolean hsbOnTop) {
|
||||
hsbBounds.height = min(bounds.height, hsb.getPreferredSize().height);
|
||||
if (hsbOnTop) {
|
||||
hsbBounds.y = bounds.y - insets.top/* + hsbBounds.height*/;
|
||||
if (hsbOpaque) bounds.y += hsbBounds.height;
|
||||
}
|
||||
else {
|
||||
hsbBounds.y = bounds.y + bounds.height + insets.bottom - hsbBounds.height;
|
||||
}
|
||||
if (hsbOpaque) bounds.height -= hsbBounds.height;
|
||||
}
|
||||
|
||||
private static int min(int one, int two) {
|
||||
return Math.max(0, Math.min(one, two));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.components;
|
||||
|
||||
import com.intellij.util.Alarm;
|
||||
import com.intellij.util.ui.Animator;
|
||||
|
||||
/**
|
||||
* @author Sergey.Malenkov
|
||||
*/
|
||||
abstract class TwoWayAnimator {
|
||||
private final Alarm myAlarm = new Alarm();
|
||||
private final MyAnimator myForwardAnimator;
|
||||
private final MyAnimator myBackwardAnimator;
|
||||
|
||||
private final int myMaxFrame;
|
||||
private int myFrame;
|
||||
|
||||
abstract void onFrame(int frame, int maxFrame);
|
||||
|
||||
TwoWayAnimator(String name, int totalFrames, int cycleDuration, int pauseForward, int pauseBackward) {
|
||||
myMaxFrame = totalFrames - 1;
|
||||
myForwardAnimator = new MyAnimator(name + "ForwardAnimator", totalFrames, cycleDuration, pauseForward, true);
|
||||
myBackwardAnimator = new MyAnimator(name + "BackwardAnimator", totalFrames, cycleDuration, pauseBackward, false);
|
||||
}
|
||||
|
||||
void startForward() {
|
||||
stop();
|
||||
myForwardAnimator.start();
|
||||
}
|
||||
|
||||
void startBackward() {
|
||||
stop();
|
||||
myBackwardAnimator.start();
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
myAlarm.cancelAllRequests();
|
||||
myForwardAnimator.suspend();
|
||||
myBackwardAnimator.suspend();
|
||||
}
|
||||
|
||||
private final class MyAnimator extends Animator implements Runnable {
|
||||
private final int myPause;
|
||||
|
||||
private MyAnimator(String name, int totalFrames, int cycleDuration, int pause, boolean forward) {
|
||||
super(name, totalFrames, cycleDuration, false, forward);
|
||||
myPause = pause;
|
||||
}
|
||||
|
||||
private void start() {
|
||||
if (isForward() ? myFrame > 0 : myFrame < myMaxFrame) {
|
||||
run();
|
||||
}
|
||||
else {
|
||||
myAlarm.addRequest(this, myPause);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
reset();
|
||||
resume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintNow(int frame, int totalFrames, int cycle) {
|
||||
if (isForward() ? (frame > myFrame) : (frame < myFrame)) {
|
||||
myFrame = frame;
|
||||
onFrame(myFrame, myMaxFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,7 @@ ide.tree.deferred.icon.invalidates.cache=true
|
||||
|
||||
ide.splitter.mouseZone=6
|
||||
ide.smart.horizontal.scrolling=true
|
||||
ide.scroll.new.layout=false
|
||||
|
||||
ide.tooltip.callout=true
|
||||
ide.tooltip.animationCycle=150
|
||||
|
||||
Reference in New Issue
Block a user