Browse Source

Made some fields available to subclasses of NumberedTextComponent.

Fixed background painting for ScrollBar overlay.
Changed selection painting to accommodate styled documents.
Added general index listener to NumberingPane.

Signed-off-by: weisj <weisj@arcor.de>
pull/15/head
weisj 5 years ago
parent
commit
a55eeb73e2
  1. 32
      src/main/java/com/github/weisj/darklaf/components/text/IndexListener.java
  2. 3
      src/main/java/com/github/weisj/darklaf/components/text/LineHighlighter.java
  3. 11
      src/main/java/com/github/weisj/darklaf/components/text/NumberedTextComponent.java
  4. 20
      src/main/java/com/github/weisj/darklaf/components/text/NumberingPane.java
  5. 4
      src/main/java/com/github/weisj/darklaf/ui/numberingpane/DarkNumberingPaneUI.java
  6. 20
      src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkScrollBarUI.java
  7. 11
      src/main/java/com/github/weisj/darklaf/ui/text/DarkCaret.java
  8. 29
      src/main/java/com/github/weisj/darklaf/ui/text/DarkHighlighter.java
  9. 2
      src/main/java/com/github/weisj/darklaf/ui/text/DarkTextFieldUI.java
  10. 6
      src/main/java/com/github/weisj/darklaf/ui/text/DarkTextUI.java
  11. 371
      src/main/java/javax/swing/text/DefaultHighlighterDark/DarkHighlightPainter.java
  12. 8
      src/test/java/ScrollPaneDemo.java

32
src/main/java/com/github/weisj/darklaf/components/text/IndexListener.java

@ -0,0 +1,32 @@
/*
* MIT License
*
* Copyright (c) 2019 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.components.text;
import java.awt.event.MouseEvent;
import java.util.EventListener;
public interface IndexListener extends EventListener {
void indexClicked(final int index, final int offset, final MouseEvent e);
}

3
src/main/java/com/github/weisj/darklaf/components/text/LineHighlighter.java

@ -46,6 +46,7 @@ public class LineHighlighter implements Highlighter.HighlightPainter, ChangeList
*/
public LineHighlighter(final JTextComponent component, final Color color) {
this.component = component;
lastView = new Rectangle(0, 0, 0, 0);
setColor(color);
}
@ -93,7 +94,7 @@ public class LineHighlighter implements Highlighter.HighlightPainter, ChangeList
Rectangle currentView = component.modelToView2D(offset).getBounds();
// Remove the highlighting from the previously highlighted line
if (lastView.y != currentView.y) {
if (lastView != null && lastView.y != currentView.y) {
component.repaint(0, lastView.y, component.getWidth(), lastView.height);
lastView = currentView;
}

11
src/main/java/com/github/weisj/darklaf/components/text/NumberedTextComponent.java

@ -31,11 +31,14 @@ import java.awt.*;
public class NumberedTextComponent extends JPanel {
private NumberingPane numberingPane;
protected final OverlayScrollPane overlayScrollPane;
protected final NumberingPane numberingPane;
protected final JTextComponent textComponent;
public NumberedTextComponent(final JTextComponent textComponent) {
super(new BorderLayout());
OverlayScrollPane overlayScrollPane = new OverlayScrollPane(textComponent);
this.textComponent = textComponent;
overlayScrollPane = new OverlayScrollPane(textComponent);
numberingPane = new NumberingPane();
numberingPane.setTextComponent(textComponent);
overlayScrollPane.getVerticalScrollBar().setBlockIncrement(textComponent.getFont().getSize());
@ -46,4 +49,8 @@ public class NumberedTextComponent extends JPanel {
public NumberingPane getNumberingPane() {
return numberingPane;
}
public JTextComponent getTextComponent() {
return textComponent;
}
}

20
src/main/java/com/github/weisj/darklaf/components/text/NumberingPane.java

@ -157,12 +157,32 @@ public class NumberingPane extends JComponent {
return pos;
}
public void addIndexListener(final IndexListener listener) {
listenerList.add(IndexListener.class, listener);
}
public void removeIndexListener(final IndexListener listener) {
listenerList.remove(IndexListener.class, listener);
}
public Collection<Icon> getIcons() {
return iconMap.values();
}
public Icon getIcon(final Position position) {
return iconMap.get(position);
}
public void removeIconAt(final Position position) {
var icon = iconMap.remove(position);
firePropertyChange("icons", icon, icon);
}
public List<IconListener> getIconListeners() {
return listenerMap.values().stream().flatMap(List::stream).collect(Collectors.toList());
}
public IndexListener[] getIndexListeners() {
return listenerList.getListeners(IndexListener.class);
}
}

4
src/main/java/com/github/weisj/darklaf/ui/numberingpane/DarkNumberingPaneUI.java

@ -266,6 +266,10 @@ public class DarkNumberingPaneUI extends ComponentUI {
}
} catch (BadLocationException ignored) { }
}
var list = numberingPane.getIndexListeners();
for (var listener : list) {
listener.indexClicked(start, offset, e);
}
}
}

20
src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkScrollBarUI.java

@ -440,10 +440,6 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
if (c.isOpaque()) {
g.setColor(scrollbar.getBackground());
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
} else {
if (c.getClientProperty("scrollBar.updateAction") != null) {
((Runnable) c.getClientProperty("scrollBar.updateAction")).run();
}
}
Graphics2D g2 = (Graphics2D) g.create();
g2.setColor(getTrackColor());
@ -530,7 +526,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
trackAlpha *= (float) (1 - (double) frame / totalFrames);
}
if (scrollbar != null) {
scrollbar.repaint();
scrollbar.getParent().repaint();
}
}
@ -538,7 +534,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
protected void paintCycleEnd() {
trackAlpha = 0;
if (scrollbar != null) {
scrollbar.repaint();
scrollbar.getParent().repaint();
}
}
};
@ -556,7 +552,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
thumbAlpha *= (float) (1 - (double) frame / totalFrames);
}
if (scrollbar != null) {
scrollbar.repaint(getThumbBounds());
scrollbar.getParent().repaint();
}
}
@ -564,7 +560,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
protected void paintCycleEnd() {
thumbAlpha = 0;
if (scrollbar != null) {
scrollbar.repaint();
scrollbar.getParent().repaint();
}
}
};
@ -578,7 +574,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
public void paintNow(final int frame, final int totalFrames, final int cycle) {
trackAlpha = ((float) frame * MAX_TRACK_ALPHA) / totalFrames;
if (scrollbar != null) {
scrollbar.repaint();
scrollbar.getParent().repaint();
}
}
@ -586,7 +582,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
protected void paintCycleEnd() {
trackAlpha = MAX_TRACK_ALPHA;
if (scrollbar != null) {
scrollbar.repaint();
scrollbar.getParent().repaint();
}
}
};
@ -601,7 +597,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
public void paintNow(final int frame, final int totalFrames, final int cycle) {
thumbAlpha = ((float) frame * MAX_THUMB_ALPHA) / totalFrames;
if (scrollbar != null) {
scrollbar.repaint(getThumbBounds());
scrollbar.getParent().repaint();
}
}
@ -609,7 +605,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
protected void paintCycleEnd() {
thumbAlpha = MAX_THUMB_ALPHA;
if (scrollbar != null) {
scrollbar.repaint();
scrollbar.getParent().repaint();
}
var p = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(p, scrollbar);

11
src/main/java/com/github/weisj/darklaf/ui/text/DarkCaret.java

@ -261,8 +261,7 @@ public class DarkCaret extends DefaultCaret implements UIResource {
}
validateWidth(r);
if (width > 0 && height > 0 &&
!contains(r.x, r.y, r.width, r.height)) {
if (width > 0 && height > 0 && !contains(r.x, r.y, r.width, r.height)) {
Rectangle clip = g.getClipBounds();
if (clip != null && !clip.contains(this)) {
// Clip doesn't contain the old location, force it
@ -295,15 +294,13 @@ public class DarkCaret extends DefaultCaret implements UIResource {
}
g.setXORMode(textAreaBg);
int y = r.y + r.height;
g.drawLine(r.x, y, r.x + r.width - 1, y);
g.fillRect(r.x, y - 1, r.width, 1);
break;
case THICK_VERTICAL_LINE_STYLE:
g.drawLine(r.x, r.y, r.x, r.y + r.height);
r.x++;
g.drawLine(r.x, r.y, r.x, r.y + r.height);
g.fillRect(r.x, r.y, 2, r.height);
break;
case VERTICAL_LINE_STYLE:
g.drawLine(r.x, r.y, r.x, r.y + r.height);
g.fillRect(r.x, r.y, 1, r.height);
break;
}
}

29
src/main/java/com/github/weisj/darklaf/ui/text/DarkHighlighter.java

@ -0,0 +1,29 @@
/*
* MIT License
*
* Copyright (c) 2019 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.text;
import javax.swing.plaf.basic.BasicTextUI;
public class DarkHighlighter extends BasicTextUI.BasicHighlighter {
}

2
src/main/java/com/github/weisj/darklaf/ui/text/DarkTextFieldUI.java

@ -256,7 +256,7 @@ public class DarkTextFieldUI extends DarkTextFieldUIBridge implements PropertyCh
@Override
protected DarkCaret.CaretStyle getDefaultCaretStyle() {
return DarkCaret.CaretStyle.THICK_VERTICAL_LINE_STYLE;
return DarkCaret.CaretStyle.VERTICAL_LINE_STYLE;
}
@Override

6
src/main/java/com/github/weisj/darklaf/ui/text/DarkTextUI.java

@ -36,6 +36,7 @@ import javax.swing.plaf.basic.BasicTextUI;
import javax.swing.text.Caret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.EditorKit;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import javax.swing.text.TextAction;
import java.awt.*;
@ -106,7 +107,10 @@ public abstract class DarkTextUI extends BasicTextUI {
super.installUI(c);
}
@Override
protected Highlighter createHighlighter() {
return new DarkHighlighter();
}
/*
* Implementation of BasicTextUI.

371
src/main/java/javax/swing/text/DefaultHighlighterDark/DarkHighlightPainter.java

@ -37,7 +37,6 @@ import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import javax.swing.text.View;
import javax.swing.text.html.InlineView;
import java.awt.*;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
@ -52,7 +51,7 @@ import java.awt.geom.Rectangle2D;
*/
public class DarkHighlightPainter extends DefaultHighlighter.DefaultHighlightPainter {
private static final boolean DEBUG_COLOR = false;
private static final boolean DEBUG_COLOR = true;
private Paint paint;
private boolean roundedEdges;
private AlphaComposite alphaComposite;
@ -184,11 +183,7 @@ public class DarkHighlightPainter extends DefaultHighlighter.DefaultHighlightPai
g2d.setComposite(getAlphaComposite());
}
try {
if (view instanceof InlineView) {
dirtyShape = paintLayerImplInline(g2d, offs0, offs1, bounds, c, view);
} else {
dirtyShape = paintLayerImpl(g2d, offs0, offs1, bounds, c, view);
}
dirtyShape = paintLayerImpl(g2d, offs0, offs1, c);
} catch (BadLocationException ignored) {
} finally {
context.restore();
@ -217,58 +212,43 @@ public class DarkHighlightPainter extends DefaultHighlighter.DefaultHighlightPai
}
protected Shape paintLayerImpl(final Graphics2D g2d, final int offs0, final int offs1,
final Shape bounds, @NotNull final JTextComponent c,
final View view) throws BadLocationException {
@NotNull final JTextComponent c) throws BadLocationException {
Shape dirtyShape;
Rectangle posStart = c.modelToView2D(c.getSelectionStart()).getBounds();
Rectangle posEnd = c.modelToView2D(c.getSelectionEnd()).getBounds();
Rectangle posEndPrev = c.modelToView2D(Math.max(0, c.getSelectionEnd() - 1)).getBounds();
Rectangle posOffs0 = c.modelToView2D(offs0).getBounds();
Rectangle posOffs0Prev = c.modelToView2D(Math.max(0, offs0 - 1)).getBounds();
Rectangle posOffs1 = c.getUI().modelToView2D(c, offs1, Position.Bias.Backward).getBounds();
Paint paint = getPaint();
if (paint == null) {
g2d.setColor(c.getSelectionColor());
} else {
g2d.setPaint(paint);
}
Rectangle posOffs1Forward = c.modelToView2D(offs1).getBounds();
Rectangle posOffs1Next = c.modelToView2D(Math.min(c.getDocument().getLength(), offs1 - 1)).getBounds();
boolean selectionStart = c.getSelectionStart() >= offs0;
boolean selectionEnd = c.getSelectionEnd() <= offs1;
if (offs0 == offs1) {
/*
* No selection. We should still paint something.
*/
if (DEBUG_COLOR) g2d.setColor(Color.YELLOW);
Shape s = view.modelToView(offs0, bounds, Position.Bias.Forward);
Rectangle r = s.getBounds();
dirtyShape = paintSelectionLine(g2d, r, c, posStart, posEnd);
} else if (posOffs0.y != posStart.y && posOffs1.y != posEnd.y
&& posOffs1.y >= posOffs0.y && posOffs1.y <= posOffs0.y + posOffs0.height) {
if (DEBUG_COLOR) g2d.setColor(Color.ORANGE);
// Contained in view, can just use bounds.
//Full lines of selection
var alloc = new Rectangle(posOffs0.x, posOffs0.y, posOffs1.x + posOffs1.width - posOffs0.x,
posOffs1.y + posOffs1.height - posOffs0.y);
dirtyShape = paintSelectionLine(g2d, alloc, c, posStart, posEnd);
} else {
// Should only render part of View.
//Start/End parts of selection
Shape shape = view.modelToView(offs0, Position.Bias.Forward, offs1, Position.Bias.Backward, bounds);
Rectangle r = shape.getBounds();
if (posEnd.y == posStart.y) {
dirtyShape = paintSelection(g2d, c, r);
} else if (posOffs0.y == posStart.y) {
dirtyShape = paintSelectionStart(g2d, r, c, posStart, posEnd);
var margin = c.getMargin();
boolean isToEndOfLine = posOffs1.y < posEnd.y && (posOffs1Forward.y != posOffs1Next.y);
boolean isToStartOfLine = !selectionEnd && posOffs0.y > posStart.y && (posOffs0.y != posOffs0Prev.y);
Rectangle alloc;
if (offs0 == offs1 && posEnd.y != posStart.y) {
alloc = new Rectangle(margin.left, posOffs0.y,
c.getWidth() - margin.left - margin.right, posOffs0.height);
} else {
dirtyShape = paintSelectionEnd(g2d, r, c, posStart, posEnd);
}
}
return dirtyShape;
alloc = new Rectangle(posOffs0.x, posOffs0.y, posOffs1.x + posOffs1.width - posOffs0.x,
posOffs1.y + posOffs1.height - posOffs0.y);
}
protected Shape paintLayerImplInline(final Graphics2D g2d, final int offs0, final int offs1,
final Shape bounds, @NotNull final JTextComponent c,
final View view) throws BadLocationException {
Shape dirtyShape;
Rectangle posStart = c.modelToView2D(c.getSelectionStart()).getBounds();
Rectangle posEnd = c.modelToView2D(c.getSelectionEnd()).getBounds();
boolean isFirstLine = alloc.y == posStart.y;
boolean isSecondLine = posStart.y + posStart.height == alloc.y;
boolean isSecondLastLine = alloc.y + alloc.height == posEnd.y;
boolean isLastLine = alloc.y == posEnd.y || isSecondLastLine && posEnd.y != posEndPrev.y;
boolean endBeforeStart = posEnd.x < (posStart.x + arcSize / 2.0)
&& (posEnd.y == posStart.y + posStart.height
|| (posEnd.y <= posStart.y + 2 * posStart.height && posEnd.x == margin.left));
alloc.width = Math.max(2 * arcSize, alloc.width);
alloc.x = Math.max(margin.left, Math.min(c.getWidth() - margin.right - alloc.width, alloc.x));
Paint paint = getPaint();
if (paint == null) {
@ -277,164 +257,117 @@ public class DarkHighlightPainter extends DefaultHighlighter.DefaultHighlightPai
g2d.setPaint(paint);
}
if (offs0 == offs1) {
if (DEBUG_COLOR) g2d.setColor(Color.YELLOW);
Shape s = view.modelToView(offs0, bounds, Position.Bias.Forward);
Rectangle r = s.getBounds();
g2d.drawLine(r.x, r.y, r.x, r.y + r.height);
dirtyShape = r;
} else if (offs0 == view.getStartOffset() && offs1 == view.getEndOffset() && posEnd.y != posStart.y) {
// Contained in view, can just use bounds.
//Full lines of selection
if (offs0 == offs1 && posEnd.y != posStart.y) {
if (DEBUG_COLOR) g2d.setColor(Color.YELLOW.darker());
isToEndOfLine = false;
isToStartOfLine = false;
dirtyShape = paintMiddleSelection(g2d, alloc, c,
false, false);
} else if (!selectionStart && !selectionEnd) {
if (DEBUG_COLOR) g2d.setColor(Color.ORANGE);
dirtyShape = paintSelectionLine(g2d, bounds.getBounds(), c, posStart, posEnd);
dirtyShape = paintMiddleSelection(g2d, alloc, c,
isToEndOfLine, isToStartOfLine);
} else {
// Should only render part of View.
//Start/End parts of selection
Shape shape = view.modelToView(offs0, Position.Bias.Forward, offs1, Position.Bias.Backward, bounds);
Rectangle r = shape.getBounds();
if ((offs0 != view.getStartOffset() && offs1 != view.getEndOffset()) || posEnd.y == posStart.y) {
dirtyShape = paintSelection(g2d, c, r);
} else if (offs1 == view.getEndOffset()) {
dirtyShape = paintSelectionStart(g2d, r, c, posStart, posEnd);
if (posEnd.y == posStart.y) {
dirtyShape = paintSelection(g2d, c, alloc, selectionStart, selectionEnd);
} else if (selectionStart) {
dirtyShape = paintSelectionStart(g2d, alloc, c, posStart, posOffs0,
endBeforeStart);
} else {
dirtyShape = paintSelectionEnd(g2d, r, c, posStart, posEnd);
dirtyShape = paintSelectionEnd(g2d, alloc, c, posStart,
isFirstLine, isSecondLine, isToStartOfLine, isToEndOfLine,
endBeforeStart);
}
}
dirtyShape = paintExtension(g2d, c,
isToEndOfLine, isToStartOfLine,
isFirstLine, isLastLine,
isSecondLine, isSecondLastLine,
selectionStart, selectionEnd,
posStart, posEnd, dirtyShape.getBounds());
return dirtyShape;
}
@NotNull
@Contract("_, _, _ -> param3")
private Shape paintSelection(final Graphics2D g2d, final JTextComponent c, final Rectangle r) {
private Shape paintMiddleSelection(final Graphics2D g, final Rectangle r, final JTextComponent c,
final boolean toEndOfLine, final boolean toStartOfLine) {
if (toStartOfLine) {
r.width -= arcSize;
r.x += arcSize;
}
if (toEndOfLine) {
r.width -= arcSize;
}
g.fillRect(r.x, r.y, r.width, r.height);
return r;
}
/*
* Selection is contained to one line.
*/
@Contract("_, _, _, _, _ -> param3")
@NotNull
private Shape paintSelection(final Graphics2D g2d, final JTextComponent c, final Rectangle r,
final boolean selectionStart, final boolean selectionEnd) {
if (DEBUG_COLOR) g2d.setColor(Color.BLUE);
if (isRounded(c)) {
g2d.fillRoundRect(r.x, r.y, r.width, r.height, arcSize, arcSize);
paintRoundedLeftRight(g2d, selectionStart, selectionEnd, r);
} else {
g2d.fillRect(r.x, r.y, r.width, r.height);
}
return r;
}
@Contract("_, _, _, _, _ -> param2")
@Contract("_, _, _, _, _, _ -> param2")
@NotNull
private Shape paintSelectionLine(@NotNull final Graphics2D g2d,
@NotNull final Rectangle alloc,
private Shape paintSelectionStart(@NotNull final Graphics2D g2d, @NotNull final Rectangle r,
@NotNull final JTextComponent c,
@NotNull final Rectangle posStart,
@NotNull final Rectangle posEnd) throws BadLocationException {
final Rectangle posOffs0,
final boolean endBeforeStart) {
if (DEBUG_COLOR) g2d.setColor(Color.RED);
var margin = c.getMargin();
boolean leftToRight = c.getComponentOrientation().isLeftToRight();
boolean isFirstLine = alloc.y == posStart.y;
boolean isSecondLine = posStart.y + posStart.height == alloc.y;
boolean isSecondLastLine = alloc.y + alloc.height == posEnd.y;
if (alloc.width == 0 && isFirstLine && !leftToRight) {
return alloc;
}
if (alloc.y == posStart.y && ((leftToRight || posStart.x - c.getWidth() - margin.right - posStart.x == 1))) {
alloc.width = c.getWidth() - margin.right - alloc.x;
} else {
alloc.width = c.getWidth() - margin.left - margin.right;
alloc.x = margin.left;
}
if (!leftToRight && alloc.y == posStart.y) {
alloc.width -= posStart.x - margin.left;
alloc.x = posStart.x;
}
if (isRounded(c)) {
boolean roundRightBottom;
boolean roundLeftTop;
boolean roundLeftBottom;
boolean roundRightTop;
if (leftToRight) {
boolean isLastLine = isSecondLastLine && posEnd.x == margin.left;
roundRightBottom = isSecondLastLine;
roundLeftTop = isFirstLine || isSecondLine && posStart.x != margin.left;
roundLeftBottom = isLastLine;
roundRightTop = isFirstLine;
} else {
boolean isLastLine = isSecondLastLine
&& (posEnd.x == c.getWidth() - margin.right - 1 || posEnd.x == margin.left);
if (c.modelToView2D(c.getSelectionEnd() - 1).getBounds().y == posEnd.y) {
isLastLine = false;
}
roundRightBottom = isLastLine || (isSecondLastLine
&& posEnd.x < (c.getWidth() - margin.right - arcSize / 2));
roundLeftBottom = isLastLine;
roundLeftTop = isFirstLine || (isSecondLine && posStart.x > margin.left + arcSize / 2);
roundRightTop = isFirstLine || isSecondLine && c.getWidth() - margin.right - posStart.x == 1;
}
if (!(roundLeftBottom || roundRightBottom || roundRightTop || roundLeftTop)) {
g2d.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);
return alloc;
}
// At least one round corner now.
g2d.fillRoundRect(alloc.x, alloc.y, alloc.width, alloc.height, arcSize, arcSize);
if (!roundRightBottom) {
g2d.fillRect(alloc.x + alloc.width - arcSize, alloc.y + alloc.height - arcSize,
arcSize, arcSize);
}
if (!roundLeftBottom) {
g2d.fillRect(alloc.x, alloc.y + alloc.height - arcSize, arcSize, arcSize);
}
if (!roundRightTop) {
g2d.fillRect(alloc.x + alloc.width - arcSize, alloc.y, arcSize, arcSize);
}
if (!roundLeftTop) {
g2d.fillRect(alloc.x, alloc.y, arcSize, arcSize);
}
boolean drawArc = isFirstLine && !roundLeftBottom;
drawArc = drawArc && alloc.x >= margin.left + arcSize;
if (drawArc) {
paintStartArc(g2d, alloc);
alloc.x -= arcSize;
alloc.width += arcSize;
paintRoundRect(g2d, r, arcSize, true, false, endBeforeStart, false);
boolean drawCorner = posOffs0.equals(posStart) && !endBeforeStart && r.x >= margin.left + arcSize;
if (drawCorner) {
paintStartArc(g2d, r);
r.x -= arcSize;
r.width += arcSize;
}
} else {
g2d.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);
g2d.fillRect(r.x, r.y, r.width, r.height);
}
return alloc;
return r;
}
private boolean isRounded(final JTextComponent c) {
return roundedEdges || Boolean.TRUE.equals(c.getClientProperty("JTextComponent.roundedSelection"));
}
@Contract("_, _, _, _, _, _, _, _, _ -> param2")
@NotNull
@Contract("_, _, _, _, _ -> param2")
private Shape paintSelectionStart(@NotNull final Graphics2D g2d, @NotNull final Rectangle r,
@NotNull final JTextComponent c,
@NotNull final Rectangle posStart, final Rectangle posEnd) {
if (DEBUG_COLOR) g2d.setColor(Color.RED);
r.width = c.getWidth() - posStart.x - c.getMargin().right;
private Shape paintSelectionEnd(@NotNull final Graphics2D g2d, @NotNull final Rectangle r,
@NotNull final JTextComponent c, final Rectangle posStart,
final boolean isFirstLine, final boolean isSecondLine,
final boolean extendToStart, final boolean extendToEnd,
final boolean endBeforeStart) {
if (DEBUG_COLOR) g2d.setColor(Color.GREEN);
var margin = c.getMargin();
if (isRounded(c)) {
boolean roundLeftBottom = posEnd.x < (posStart.x + 2) && posEnd.y == posStart.y + posStart.height;
boolean roundRightBottom = posEnd.y == posStart.y + posStart.height;
if (!c.getComponentOrientation().isLeftToRight()) {
roundLeftBottom = roundLeftBottom || c.getWidth() - c.getMargin().right - posEnd.x == 1;
}
g2d.fillRoundRect(r.x, r.y, r.width, r.height, arcSize, arcSize);
if (!roundLeftBottom && !roundRightBottom) {
//Optimize draw calls
g2d.fillRect(r.x, r.y + r.height - arcSize, r.width, arcSize);
} else if (!roundLeftBottom) {
g2d.fillRect(r.x, r.y + r.height - arcSize, arcSize, arcSize);
} else if (!roundRightBottom) {
g2d.fillRect(r.x + r.width - arcSize, r.y + r.height - arcSize, arcSize, arcSize);
if (r.x + r.width >= c.getWidth() - margin.right - arcSize / 2.0) {
int end = c.getWidth() - margin.right;
r.width = end - r.x;
}
boolean drawCorner = !roundLeftBottom && r.x >= margin.left + arcSize;
if (isRounded(c)) {
boolean roundRightTop = endBeforeStart && !extendToEnd;
boolean roundLeftBottom = !isFirstLine && !extendToStart;
boolean roundLeftTop = isSecondLine && !extendToStart && posStart.x >= r.x + arcSize;
paintRoundRect(g2d, r, arcSize, roundLeftTop, roundRightTop, roundLeftBottom, !extendToEnd);
boolean drawCorner = !extendToEnd && !roundRightTop && r.x + r.width <= c.getWidth() - margin.right - arcSize;
if (drawCorner) {
paintStartArc(g2d, r);
r.x -= arcSize;
paintEndArc(g2d, r);
r.width += arcSize;
}
} else {
@ -443,48 +376,80 @@ public class DarkHighlightPainter extends DefaultHighlighter.DefaultHighlightPai
return r;
}
@Contract("_, _, _, _, _ -> param2")
@NotNull
private Shape paintSelectionEnd(@NotNull final Graphics2D g2d, @NotNull final Rectangle r,
@NotNull final JTextComponent c, final Rectangle posStart,
final Rectangle posEnd) {
if (DEBUG_COLOR) g2d.setColor(Color.GREEN);
private Shape paintExtension(final Graphics2D g2d, @NotNull final JTextComponent c,
final boolean isToEndOfLine, final boolean isToStartOfLine,
final boolean isFirstLine, final boolean isLastLine,
final boolean isSecondLine, final boolean isSecondLastLine,
final boolean selectionStart, final boolean selectionEnd,
final Rectangle posStart, final Rectangle posEnd,
final Rectangle r) {
var margin = c.getMargin();
r.width += r.x - margin.left;
r.x = margin.left;
if (!c.getComponentOrientation().isLeftToRight() && r.y == posStart.y) {
r.width -= posStart.x - margin.left;
r.x = posStart.x;
boolean rounded = isRounded(c);
if (isToEndOfLine) {
if (DEBUG_COLOR) g2d.setColor(Color.CYAN);
int start = r.x + r.width;
int w = c.getWidth() - start - margin.right;
w = Math.max(2 * arcSize, w);
start = Math.min(start, c.getWidth() - margin.right - w);
if (rounded) {
boolean roundTop = isFirstLine || selectionStart;
boolean roundBottom = isLastLine || (isSecondLastLine && posEnd.x + posEnd.width <= start + w - arcSize);
boolean roundLeftTop = isFirstLine && start == margin.left;
paintRoundRect(g2d, new Rectangle(start, r.y, w, r.height), arcSize,
roundLeftTop, roundTop, false, roundBottom);
} else {
g2d.fillRect(start, r.y, w, r.height);
}
r.x = Math.min(r.x, start);
r.width += w;
}
if (isToStartOfLine) {
if (DEBUG_COLOR) g2d.setColor(Color.CYAN.darker());
int start = margin.left;
int end = r.x;
int w = end - start;
w = Math.max(2 * arcSize, w);
end = Math.max(end, start + w);
if (rounded) {
boolean roundTop = isFirstLine || (isSecondLine && posStart.x >= start + arcSize);
boolean roundBottom = isLastLine || selectionEnd;
boolean roundRightBottom = isLastLine && end == c.getWidth() - margin.right;
paintRoundRect(g2d, new Rectangle(start, r.y, w, r.height), arcSize,
roundTop, false, roundBottom, roundRightBottom);
} else {
g2d.fillRect(r.x, r.y, end - r.x, r.height);
}
if (!c.getComponentOrientation().isLeftToRight()) {
if (posEnd.x >= (c.getWidth() - margin.right - arcSize / 2)) {
//Fill to right edge even if corners are not rounded.
r.width = c.getWidth() - margin.right - margin.left;
r.width += w;
r.x = start;
}
return r;
}
if (isRounded(c)) {
boolean roundRightTop = posEnd.x < (posStart.x + 2) && posEnd.y == posStart.y + posStart.height;
boolean roundLeftTop = posEnd.y == posStart.y + posStart.height && posStart.x != margin.left;
g2d.fillRoundRect(r.x, r.y, r.width, r.height, arcSize, arcSize);
if (!roundLeftTop && !roundRightTop) {
//Optimize draw calls
g2d.fillRect(r.x, r.y, r.width, arcSize);
} else if (!roundLeftTop) {
g2d.fillRect(r.x, r.y, arcSize, arcSize);
} else if (!roundRightTop) {
g2d.fillRect(r.x + r.width - arcSize, r.y, arcSize, arcSize);
}
boolean drawCorner = !roundRightTop && r.x + r.width <= c.getWidth() - margin.right - arcSize;
if (drawCorner) {
paintEndArc(g2d, r);
r.width += arcSize;
private void paintRoundRect(@NotNull final Graphics g, @NotNull final Rectangle r, final int arcSize,
final boolean leftTop, final boolean rightTop,
final boolean leftBottom, final boolean rightBottom) {
int aw = Math.min(arcSize, r.width);
int ah = Math.min(arcSize, r.height);
g.fillRoundRect(r.x, r.y, r.width, r.height, aw, ah);
if (!leftTop) g.fillRect(r.x, r.y, aw, ah);
if (!leftBottom) g.fillRect(r.x, r.y + r.height - ah, aw, ah);
if (!rightTop) g.fillRect(r.x + r.width - aw, r.y, aw, ah);
if (!rightBottom) g.fillRect(r.x + r.width - aw, r.y + r.height - ah, aw, ah);
}
private void paintRoundedLeftRight(final Graphics g, final boolean left, final boolean right, final Rectangle r) {
if (right || left) {
g.fillRoundRect(r.x, r.y, r.width, r.height, arcSize, arcSize);
g.setColor(Color.PINK);
if (!left) {
g.fillRect(r.x, r.y, arcSize, r.height);
}
if (!right) {
g.fillRect(r.x + r.width - arcSize, r.y, arcSize, r.height);
}
} else {
g2d.fillRect(r.x, r.y, r.width, r.height);
g.fillRect(r.x, r.y, r.width, r.height);
}
return r;
}
private void paintStartArc(@NotNull final Graphics2D g2d, @NotNull final Rectangle r) {

8
src/test/java/ScrollPaneDemo.java

@ -15,12 +15,14 @@ public final class ScrollPaneDemo extends MultiSplitLayout {
LafManager.install();
final var frame = new JFrame();
frame.setSize(500, 500);
var overlayScroll = new JScrollPane(new JEditorPane() {{
// setEditorKit(new HTMLEditorKit());
var overlayScroll = new JTextPane() {{
setText(TestResources.LOREM_IPSUM.repeat(10));
setFont(Font.getFont(Font.MONOSPACED));
// setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
}});
// SimpleAttributeSet attribs = new SimpleAttributeSet();
// StyleConstants.setAlignment(attribs, StyleConstants.ALIGN_RIGHT);
// setParagraphAttributes(attribs, true);
}};
frame.setContentPane(new JPanel(new BorderLayout()) {{
add(overlayScroll, BorderLayout.CENTER);
}});

Loading…
Cancel
Save