Browse Source

Animate tree control icons.

Fix disabled expanded tree control appearing collapsed.
pull/214/head
weisj 4 years ago
parent
commit
d6e89f86e0
  1. 113
      core/src/main/java/com/github/weisj/darklaf/ui/tree/DarkTreeExpansionAnimationListener.java
  2. 52
      core/src/main/java/com/github/weisj/darklaf/ui/tree/DarkTreeUI.java
  3. 1
      core/src/main/resources/com/github/weisj/darklaf/properties/overwrites.properties
  4. 17
      core/src/main/resources/com/github/weisj/darklaf/properties/ui/tree.properties

113
core/src/main/java/com/github/weisj/darklaf/ui/tree/DarkTreeExpansionAnimationListener.java

@ -0,0 +1,113 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.github.weisj.darklaf.ui.tree;
import java.awt.*;
import javax.swing.*;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.TreePath;
import com.github.weisj.darklaf.graphics.Animator;
public class DarkTreeExpansionAnimationListener implements TreeExpansionListener {
private final JTree tree;
private final TreeStateAnimator animator;
public DarkTreeExpansionAnimationListener(final JTree tree) {
this.tree = tree;
this.animator = new TreeStateAnimator();
animator.setEnabled(UIManager.getBoolean("Tree.iconAnimations"));
tree.addTreeExpansionListener(this);
}
@Override
public void treeExpanded(final TreeExpansionEvent event) {
startAnimation(event.getPath());
}
@Override
public void treeCollapsed(final TreeExpansionEvent event) {
startAnimation(event.getPath());
}
public void startAnimation(final TreePath path) {
if (!animator.isEnabled()) return;
animator.state = 0;
animator.path = path;
animator.animationRow = tree.getRowForPath(path);
boolean running = animator.isRunning();
animator.suspend();
if (running) {
// Forces paintCycleEnd to be called.
animator.resume(animator.getTotalFrames());
}
animator.resume(0, tree);
}
public TreePath getAnimationPath() {
return animator.path;
}
public float getAnimationState() {
return animator.state;
}
protected class TreeStateAnimator extends Animator {
private static final int DURATION = 60;
private static final int RESOLUTION = 10;
private TreePath path;
private float state;
private int animationRow;
public TreeStateAnimator() {
super(DURATION / RESOLUTION, DURATION, 0);
}
private void repaint() {
if (animationRow >= 0) {
Rectangle bounds = tree.getRowBounds(animationRow);
bounds.x = 0;
bounds.width = tree.getWidth();
tree.paintImmediately(bounds);
}
}
@Override
public void paintNow(final float fraction) {
state = fraction;
repaint();
}
@Override
protected void paintCycleEnd() {
super.paintCycleEnd();
state = 1;
repaint();
animationRow = -1;
}
}
}

52
core/src/main/java/com/github/weisj/darklaf/ui/tree/DarkTreeUI.java

@ -32,12 +32,10 @@ import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import javax.swing.tree.*;
import com.github.weisj.darklaf.graphics.PaintUtil;
import com.github.weisj.darklaf.icons.RotatableIcon;
import com.github.weisj.darklaf.ui.cell.CellConstants;
import com.github.weisj.darklaf.ui.cell.CellUtil;
import com.github.weisj.darklaf.ui.cell.DarkCellRendererPane;
@ -66,6 +64,7 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
public static final String KEY_IS_TABLE_TREE = "JComponent.isTableTree";
protected static final Rectangle boundsBuffer = new Rectangle();
protected static final RotatableIcon paintingIcon = new RotatableIcon();
protected MouseListener selectionListener;
protected Color lineColor;
@ -92,6 +91,7 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
private int dashLength;
private int dashGapLength;
private DarkTreeExpansionAnimationListener treeExpansionAnimationListener;
public static ComponentUI createUI(final JComponent c) {
return new DarkTreeUI();
@ -162,6 +162,12 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
tree.addPropertyChangeListener(this);
selectionListener = createMouseSelectionListener();
tree.addMouseListener(selectionListener);
this.treeExpansionAnimationListener = createExpansionAnimationListener();
tree.addTreeExpansionListener(treeExpansionAnimationListener);
}
protected DarkTreeExpansionAnimationListener createExpansionAnimationListener() {
return new DarkTreeExpansionAnimationListener(tree);
}
protected MouseListener createMouseSelectionListener() {
@ -247,6 +253,8 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
tree.removeMouseListener(selectionListener);
selectionListener = null;
tree.removePropertyChangeListener(this);
tree.removeTreeExpansionListener(treeExpansionAnimationListener);
treeExpansionAnimationListener = null;
}
@Override
@ -254,6 +262,7 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
if (tree != c) {
throw new InternalError("incorrect component");
}
// Should never happen if installed for a UI
if (treeState == null) {
return;
@ -270,7 +279,8 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
boolean done = false;
while (!done && paintingEnumerator.hasMoreElements()) {
TreePath path = (TreePath) paintingEnumerator.nextElement();
if (!paintSingleRow(g, paintBounds, insets, path, row)) {
Rectangle cellBounds = paintSingleRow(g, paintBounds, insets, path, row);
if (cellBounds == null || (cellBounds.y + cellBounds.height) >= paintBounds.y + paintBounds.height) {
done = true;
}
row++;
@ -293,15 +303,15 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
g.translate(0, rowBounds.y);
}
protected boolean paintSingleRow(final Graphics g, final Rectangle paintBounds, final Insets insets,
protected Rectangle paintSingleRow(final Graphics g, final Rectangle paintBounds, final Insets insets,
final TreePath path, final int row) {
if (path == null) return false;
if (path == null) return null;
final int xOffset =
tree.getParent() instanceof JViewport ? ((JViewport) tree.getParent()).getViewPosition().x : 0;
final int containerWidth =
tree.getParent() instanceof JViewport ? tree.getParent().getWidth() : tree.getWidth();
final Rectangle cellBounds = getPathBounds(path, insets, boundsBuffer);
if (cellBounds == null) return false;
if (cellBounds == null) return null;
final int boundsX = cellBounds.x;
final int boundsWidth = cellBounds.width;
@ -333,7 +343,7 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
PaintUtil.drawRect(g, cellBounds, leadSelectionBorderInsets);
}
return (cellBounds.y + cellBounds.height) < paintBounds.y + paintBounds.height;
return cellBounds;
}
protected void paintRowBackground(final Graphics g, final Rectangle bounds, final TreePath path, final int row,
@ -519,11 +529,27 @@ public class DarkTreeUI extends BasicTreeUI implements PropertyChangeListener, C
int iconCenterY = bounds.y + (bounds.height / 2);
if (isExpanded) {
Icon expandedIcon = getExpandedIcon();
if (expandedIcon != null) drawCentered(tree, g, expandedIcon, iconCenterX, iconCenterY);
Icon expIcon = getExpandedIcon();
if (expIcon != null) {
if (Objects.equals(treeExpansionAnimationListener.getAnimationPath(), path)) {
paintingIcon.setIcon(expIcon);
paintingIcon
.setRotation((treeExpansionAnimationListener.getAnimationState() - 1) * Math.PI / 2.0);
expIcon = paintingIcon;
}
drawCentered(tree, g, expIcon, iconCenterX, iconCenterY);
}
} else {
Icon collapsedIcon = getCollapsedIcon();
if (collapsedIcon != null) drawCentered(tree, g, collapsedIcon, iconCenterX, iconCenterY);
Icon collIcon = getCollapsedIcon();
if (collIcon != null) {
if (Objects.equals(treeExpansionAnimationListener.getAnimationPath(), path)) {
paintingIcon.setIcon(collIcon);
paintingIcon
.setRotation((1 - treeExpansionAnimationListener.getAnimationState()) * Math.PI / 2.0);
collIcon = paintingIcon;
}
drawCentered(tree, g, collIcon, iconCenterX, iconCenterY);
}
}
}
}

1
core/src/main/resources/com/github/weisj/darklaf/properties/overwrites.properties

@ -35,3 +35,4 @@ ToolTip.defaultStyle = tooltipStyle
TitlePane.unifiedMenuBar = unifiedMenuBar
ToggleButton.animated = animations
ScrollBar.animated = animations
Tree.iconAnimations = animations

17
core/src/main/resources/com/github/weisj/darklaf/properties/ui/tree.properties

@ -34,6 +34,7 @@ Tree.hash = %borderFocus
Tree.lineFocusSelected = %borderFocus
Tree.lineSelected = %gridLine
Tree.lineUnselected = %gridLine
Tree.iconAnimations = true
Tree.background = %Cell.background
Tree.backgroundAlternative = %Cell.backgroundAlternative
@ -84,12 +85,12 @@ Tree.leafIcon = files/general.svg[themed]
Tree.collapsed.selected.focused.icon = navigation/arrow/thin/arrowRightSelected.svg[themed]
Tree.collapsed.selected.unfocused.icon = navigation/arrow/thin/arrowRight.svg[themed]
Tree.collapsed.unselected.focused.icon = navigation/arrow/thin/arrowRight.svg[themed]
Tree.collapsed.unselected.unfocused.icon = navigation/arrow/thin/arrowRight.svg[themed]
Tree.collapsed.disabled.icon = navigation/arrow/thin/arrowRightDisabled.svg[themed]
Tree.collapsed.unselected.focused.icon = navigation/arrow/thin/arrowRight.svg[themed]
Tree.collapsed.unselected.unfocused.icon = navigation/arrow/thin/arrowRight.svg[themed]
Tree.collapsed.disabled.icon = navigation/arrow/thin/arrowRightDisabled.svg[themed]
Tree.expanded.selected.focused.icon = navigation/arrow/thin/arrowDownSelected.svg[themed]
Tree.expanded.selected.unfocused.icon = navigation/arrow/thin/arrowDown.svg[themed]
Tree.expanded.unselected.focused.icon = navigation/arrow/thin/arrowDown.svg[themed]
Tree.expanded.unselected.unfocused.icon = navigation/arrow/thin/arrowDown.svg[themed]
Tree.expanded.disabled.icon = navigation/arrow/thin/arrowRightDisabled.svg[themed]
Tree.expanded.selected.focused.icon = navigation/arrow/thin/arrowDownSelected.svg[themed]
Tree.expanded.selected.unfocused.icon = navigation/arrow/thin/arrowDown.svg[themed]
Tree.expanded.unselected.focused.icon = navigation/arrow/thin/arrowDown.svg[themed]
Tree.expanded.unselected.unfocused.icon = navigation/arrow/thin/arrowDown.svg[themed]
Tree.expanded.disabled.icon = navigation/arrow/thin/arrowDownDisabled.svg[themed]

Loading…
Cancel
Save