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) { public LineHighlighter(final JTextComponent component, final Color color) {
this.component = component; this.component = component;
lastView = new Rectangle(0, 0, 0, 0);
setColor(color); setColor(color);
} }
@ -93,7 +94,7 @@ public class LineHighlighter implements Highlighter.HighlightPainter, ChangeList
Rectangle currentView = component.modelToView2D(offset).getBounds(); Rectangle currentView = component.modelToView2D(offset).getBounds();
// Remove the highlighting from the previously highlighted line // 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); component.repaint(0, lastView.y, component.getWidth(), lastView.height);
lastView = currentView; 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 { 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) { public NumberedTextComponent(final JTextComponent textComponent) {
super(new BorderLayout()); super(new BorderLayout());
OverlayScrollPane overlayScrollPane = new OverlayScrollPane(textComponent); this.textComponent = textComponent;
overlayScrollPane = new OverlayScrollPane(textComponent);
numberingPane = new NumberingPane(); numberingPane = new NumberingPane();
numberingPane.setTextComponent(textComponent); numberingPane.setTextComponent(textComponent);
overlayScrollPane.getVerticalScrollBar().setBlockIncrement(textComponent.getFont().getSize()); overlayScrollPane.getVerticalScrollBar().setBlockIncrement(textComponent.getFont().getSize());
@ -46,4 +49,8 @@ public class NumberedTextComponent extends JPanel {
public NumberingPane getNumberingPane() { public NumberingPane getNumberingPane() {
return numberingPane; 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; 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() { public Collection<Icon> getIcons() {
return iconMap.values(); return iconMap.values();
} }
public Icon getIcon(final Position position) {
return iconMap.get(position);
}
public void removeIconAt(final Position position) { public void removeIconAt(final Position position) {
var icon = iconMap.remove(position); var icon = iconMap.remove(position);
firePropertyChange("icons", icon, icon); 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) { } } 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()) { if (c.isOpaque()) {
g.setColor(scrollbar.getBackground()); g.setColor(scrollbar.getBackground());
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); 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(); Graphics2D g2 = (Graphics2D) g.create();
g2.setColor(getTrackColor()); g2.setColor(getTrackColor());
@ -530,7 +526,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
trackAlpha *= (float) (1 - (double) frame / totalFrames); trackAlpha *= (float) (1 - (double) frame / totalFrames);
} }
if (scrollbar != null) { if (scrollbar != null) {
scrollbar.repaint(); scrollbar.getParent().repaint();
} }
} }
@ -538,7 +534,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
protected void paintCycleEnd() { protected void paintCycleEnd() {
trackAlpha = 0; trackAlpha = 0;
if (scrollbar != null) { if (scrollbar != null) {
scrollbar.repaint(); scrollbar.getParent().repaint();
} }
} }
}; };
@ -556,7 +552,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
thumbAlpha *= (float) (1 - (double) frame / totalFrames); thumbAlpha *= (float) (1 - (double) frame / totalFrames);
} }
if (scrollbar != null) { if (scrollbar != null) {
scrollbar.repaint(getThumbBounds()); scrollbar.getParent().repaint();
} }
} }
@ -564,7 +560,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
protected void paintCycleEnd() { protected void paintCycleEnd() {
thumbAlpha = 0; thumbAlpha = 0;
if (scrollbar != null) { 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) { public void paintNow(final int frame, final int totalFrames, final int cycle) {
trackAlpha = ((float) frame * MAX_TRACK_ALPHA) / totalFrames; trackAlpha = ((float) frame * MAX_TRACK_ALPHA) / totalFrames;
if (scrollbar != null) { if (scrollbar != null) {
scrollbar.repaint(); scrollbar.getParent().repaint();
} }
} }
@ -586,7 +582,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
protected void paintCycleEnd() { protected void paintCycleEnd() {
trackAlpha = MAX_TRACK_ALPHA; trackAlpha = MAX_TRACK_ALPHA;
if (scrollbar != null) { 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) { public void paintNow(final int frame, final int totalFrames, final int cycle) {
thumbAlpha = ((float) frame * MAX_THUMB_ALPHA) / totalFrames; thumbAlpha = ((float) frame * MAX_THUMB_ALPHA) / totalFrames;
if (scrollbar != null) { if (scrollbar != null) {
scrollbar.repaint(getThumbBounds()); scrollbar.getParent().repaint();
} }
} }
@ -609,7 +605,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI {
protected void paintCycleEnd() { protected void paintCycleEnd() {
thumbAlpha = MAX_THUMB_ALPHA; thumbAlpha = MAX_THUMB_ALPHA;
if (scrollbar != null) { if (scrollbar != null) {
scrollbar.repaint(); scrollbar.getParent().repaint();
} }
var p = MouseInfo.getPointerInfo().getLocation(); var p = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(p, scrollbar); 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); validateWidth(r);
if (width > 0 && height > 0 && if (width > 0 && height > 0 && !contains(r.x, r.y, r.width, r.height)) {
!contains(r.x, r.y, r.width, r.height)) {
Rectangle clip = g.getClipBounds(); Rectangle clip = g.getClipBounds();
if (clip != null && !clip.contains(this)) { if (clip != null && !clip.contains(this)) {
// Clip doesn't contain the old location, force it // Clip doesn't contain the old location, force it
@ -295,15 +294,13 @@ public class DarkCaret extends DefaultCaret implements UIResource {
} }
g.setXORMode(textAreaBg); g.setXORMode(textAreaBg);
int y = r.y + r.height; 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; break;
case THICK_VERTICAL_LINE_STYLE: case THICK_VERTICAL_LINE_STYLE:
g.drawLine(r.x, r.y, r.x, r.y + r.height); g.fillRect(r.x, r.y, 2, r.height);
r.x++;
g.drawLine(r.x, r.y, r.x, r.y + r.height);
break; break;
case VERTICAL_LINE_STYLE: 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; 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 @Override
protected DarkCaret.CaretStyle getDefaultCaretStyle() { protected DarkCaret.CaretStyle getDefaultCaretStyle() {
return DarkCaret.CaretStyle.THICK_VERTICAL_LINE_STYLE; return DarkCaret.CaretStyle.VERTICAL_LINE_STYLE;
} }
@Override @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.Caret;
import javax.swing.text.DefaultEditorKit; import javax.swing.text.DefaultEditorKit;
import javax.swing.text.EditorKit; import javax.swing.text.EditorKit;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent; import javax.swing.text.JTextComponent;
import javax.swing.text.TextAction; import javax.swing.text.TextAction;
import java.awt.*; import java.awt.*;
@ -106,7 +107,10 @@ public abstract class DarkTextUI extends BasicTextUI {
super.installUI(c); super.installUI(c);
} }
@Override
protected Highlighter createHighlighter() {
return new DarkHighlighter();
}
/* /*
* Implementation of BasicTextUI. * 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.JTextComponent;
import javax.swing.text.Position; import javax.swing.text.Position;
import javax.swing.text.View; import javax.swing.text.View;
import javax.swing.text.html.InlineView;
import java.awt.*; import java.awt.*;
import java.awt.geom.Arc2D; import java.awt.geom.Arc2D;
import java.awt.geom.Area; import java.awt.geom.Area;
@ -52,7 +51,7 @@ import java.awt.geom.Rectangle2D;
*/ */
public class DarkHighlightPainter extends DefaultHighlighter.DefaultHighlightPainter { public class DarkHighlightPainter extends DefaultHighlighter.DefaultHighlightPainter {
private static final boolean DEBUG_COLOR = false; private static final boolean DEBUG_COLOR = true;
private Paint paint; private Paint paint;
private boolean roundedEdges; private boolean roundedEdges;
private AlphaComposite alphaComposite; private AlphaComposite alphaComposite;
@ -184,11 +183,7 @@ public class DarkHighlightPainter extends DefaultHighlighter.DefaultHighlightPai
g2d.setComposite(getAlphaComposite()); g2d.setComposite(getAlphaComposite());
} }
try { try {
if (view instanceof InlineView) { dirtyShape = paintLayerImpl(g2d, offs0, offs1, c);
dirtyShape = paintLayerImplInline(g2d, offs0, offs1, bounds, c, view);
} else {
dirtyShape = paintLayerImpl(g2d, offs0, offs1, bounds, c, view);
}
} catch (BadLocationException ignored) { } catch (BadLocationException ignored) {
} finally { } finally {
context.restore(); context.restore();
@ -217,58 +212,43 @@ public class DarkHighlightPainter extends DefaultHighlighter.DefaultHighlightPai
} }
protected Shape paintLayerImpl(final Graphics2D g2d, final int offs0, final int offs1, protected Shape paintLayerImpl(final Graphics2D g2d, final int offs0, final int offs1,
final Shape bounds, @NotNull final JTextComponent c, @NotNull final JTextComponent c) throws BadLocationException {
final View view) throws BadLocationException {
Shape dirtyShape; Shape dirtyShape;
Rectangle posStart = c.modelToView2D(c.getSelectionStart()).getBounds(); Rectangle posStart = c.modelToView2D(c.getSelectionStart()).getBounds();
Rectangle posEnd = c.modelToView2D(c.getSelectionEnd()).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 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(); Rectangle posOffs1 = c.getUI().modelToView2D(c, offs1, Position.Bias.Backward).getBounds();
Paint paint = getPaint(); Rectangle posOffs1Forward = c.modelToView2D(offs1).getBounds();
if (paint == null) { Rectangle posOffs1Next = c.modelToView2D(Math.min(c.getDocument().getLength(), offs1 - 1)).getBounds();
g2d.setColor(c.getSelectionColor()); boolean selectionStart = c.getSelectionStart() >= offs0;
} else { boolean selectionEnd = c.getSelectionEnd() <= offs1;
g2d.setPaint(paint);
}
if (offs0 == offs1) { var margin = c.getMargin();
/*
* No selection. We should still paint something. boolean isToEndOfLine = posOffs1.y < posEnd.y && (posOffs1Forward.y != posOffs1Next.y);
*/ boolean isToStartOfLine = !selectionEnd && posOffs0.y > posStart.y && (posOffs0.y != posOffs0Prev.y);
if (DEBUG_COLOR) g2d.setColor(Color.YELLOW);
Shape s = view.modelToView(offs0, bounds, Position.Bias.Forward); Rectangle alloc;
Rectangle r = s.getBounds(); if (offs0 == offs1 && posEnd.y != posStart.y) {
dirtyShape = paintSelectionLine(g2d, r, c, posStart, posEnd); alloc = new Rectangle(margin.left, posOffs0.y,
} else if (posOffs0.y != posStart.y && posOffs1.y != posEnd.y c.getWidth() - margin.left - margin.right, posOffs0.height);
&& 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);
} else { } else {
dirtyShape = paintSelectionEnd(g2d, r, c, posStart, posEnd); alloc = new Rectangle(posOffs0.x, posOffs0.y, posOffs1.x + posOffs1.width - posOffs0.x,
} posOffs1.y + posOffs1.height - posOffs0.y);
}
return dirtyShape;
} }
protected Shape paintLayerImplInline(final Graphics2D g2d, final int offs0, final int offs1, boolean isFirstLine = alloc.y == posStart.y;
final Shape bounds, @NotNull final JTextComponent c, boolean isSecondLine = posStart.y + posStart.height == alloc.y;
final View view) throws BadLocationException { boolean isSecondLastLine = alloc.y + alloc.height == posEnd.y;
Shape dirtyShape; boolean isLastLine = alloc.y == posEnd.y || isSecondLastLine && posEnd.y != posEndPrev.y;
Rectangle posStart = c.modelToView2D(c.getSelectionStart()).getBounds(); boolean endBeforeStart = posEnd.x < (posStart.x + arcSize / 2.0)
Rectangle posEnd = c.modelToView2D(c.getSelectionEnd()).getBounds(); && (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(); Paint paint = getPaint();
if (paint == null) { if (paint == null) {
@ -277,164 +257,117 @@ public class DarkHighlightPainter extends DefaultHighlighter.DefaultHighlightPai
g2d.setPaint(paint); g2d.setPaint(paint);
} }
if (offs0 == offs1) { if (offs0 == offs1 && posEnd.y != posStart.y) {
if (DEBUG_COLOR) g2d.setColor(Color.YELLOW); if (DEBUG_COLOR) g2d.setColor(Color.YELLOW.darker());
Shape s = view.modelToView(offs0, bounds, Position.Bias.Forward); isToEndOfLine = false;
Rectangle r = s.getBounds(); isToStartOfLine = false;
g2d.drawLine(r.x, r.y, r.x, r.y + r.height); dirtyShape = paintMiddleSelection(g2d, alloc, c,
dirtyShape = r; false, false);
} else if (offs0 == view.getStartOffset() && offs1 == view.getEndOffset() && posEnd.y != posStart.y) { } else if (!selectionStart && !selectionEnd) {
// Contained in view, can just use bounds.
//Full lines of selection
if (DEBUG_COLOR) g2d.setColor(Color.ORANGE); if (DEBUG_COLOR) g2d.setColor(Color.ORANGE);
dirtyShape = paintSelectionLine(g2d, bounds.getBounds(), c, posStart, posEnd); dirtyShape = paintMiddleSelection(g2d, alloc, c,
isToEndOfLine, isToStartOfLine);
} else { } else {
// Should only render part of View. // Should only render part of View.
//Start/End parts of selection //Start/End parts of selection
Shape shape = view.modelToView(offs0, Position.Bias.Forward, offs1, Position.Bias.Backward, bounds); if (posEnd.y == posStart.y) {
Rectangle r = shape.getBounds(); dirtyShape = paintSelection(g2d, c, alloc, selectionStart, selectionEnd);
if ((offs0 != view.getStartOffset() && offs1 != view.getEndOffset()) || posEnd.y == posStart.y) { } else if (selectionStart) {
dirtyShape = paintSelection(g2d, c, r); dirtyShape = paintSelectionStart(g2d, alloc, c, posStart, posOffs0,
} else if (offs1 == view.getEndOffset()) { endBeforeStart);
dirtyShape = paintSelectionStart(g2d, r, c, posStart, posEnd);
} else { } 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; return dirtyShape;
} }
@NotNull @NotNull
@Contract("_, _, _ -> param3") private Shape paintMiddleSelection(final Graphics2D g, final Rectangle r, final JTextComponent c,
private Shape paintSelection(final Graphics2D g2d, final JTextComponent c, final Rectangle r) { 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. * 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 (DEBUG_COLOR) g2d.setColor(Color.BLUE);
if (isRounded(c)) { if (isRounded(c)) {
g2d.fillRoundRect(r.x, r.y, r.width, r.height, arcSize, arcSize); paintRoundedLeftRight(g2d, selectionStart, selectionEnd, r);
} else { } else {
g2d.fillRect(r.x, r.y, r.width, r.height); g2d.fillRect(r.x, r.y, r.width, r.height);
} }
return r; return r;
} }
@Contract("_, _, _, _, _ -> param2") @Contract("_, _, _, _, _, _ -> param2")
@NotNull @NotNull
private Shape paintSelectionLine(@NotNull final Graphics2D g2d, private Shape paintSelectionStart(@NotNull final Graphics2D g2d, @NotNull final Rectangle r,
@NotNull final Rectangle alloc,
@NotNull final JTextComponent c, @NotNull final JTextComponent c,
@NotNull final Rectangle posStart, @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(); 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)) { if (isRounded(c)) {
boolean roundRightBottom; paintRoundRect(g2d, r, arcSize, true, false, endBeforeStart, false);
boolean roundLeftTop; boolean drawCorner = posOffs0.equals(posStart) && !endBeforeStart && r.x >= margin.left + arcSize;
boolean roundLeftBottom; if (drawCorner) {
boolean roundRightTop; paintStartArc(g2d, r);
if (leftToRight) { r.x -= arcSize;
boolean isLastLine = isSecondLastLine && posEnd.x == margin.left; r.width += arcSize;
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;
} }
} else { } 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) { private boolean isRounded(final JTextComponent c) {
return roundedEdges || Boolean.TRUE.equals(c.getClientProperty("JTextComponent.roundedSelection")); return roundedEdges || Boolean.TRUE.equals(c.getClientProperty("JTextComponent.roundedSelection"));
} }
@Contract("_, _, _, _, _, _, _, _, _ -> param2")
@NotNull @NotNull
@Contract("_, _, _, _, _ -> param2") private Shape paintSelectionEnd(@NotNull final Graphics2D g2d, @NotNull final Rectangle r,
private Shape paintSelectionStart(@NotNull final Graphics2D g2d, @NotNull final Rectangle r, @NotNull final JTextComponent c, final Rectangle posStart,
@NotNull final JTextComponent c, final boolean isFirstLine, final boolean isSecondLine,
@NotNull final Rectangle posStart, final Rectangle posEnd) { final boolean extendToStart, final boolean extendToEnd,
if (DEBUG_COLOR) g2d.setColor(Color.RED); final boolean endBeforeStart) {
if (DEBUG_COLOR) g2d.setColor(Color.GREEN);
r.width = c.getWidth() - posStart.x - c.getMargin().right;
var margin = c.getMargin(); var margin = c.getMargin();
if (isRounded(c)) { if (r.x + r.width >= c.getWidth() - margin.right - arcSize / 2.0) {
boolean roundLeftBottom = posEnd.x < (posStart.x + 2) && posEnd.y == posStart.y + posStart.height; int end = c.getWidth() - margin.right;
boolean roundRightBottom = posEnd.y == posStart.y + posStart.height; r.width = end - r.x;
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);
} }
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) { if (drawCorner) {
paintStartArc(g2d, r); paintEndArc(g2d, r);
r.x -= arcSize;
r.width += arcSize; r.width += arcSize;
} }
} else { } else {
@ -443,48 +376,80 @@ public class DarkHighlightPainter extends DefaultHighlighter.DefaultHighlightPai
return r; return r;
} }
@Contract("_, _, _, _, _ -> param2") private Shape paintExtension(final Graphics2D g2d, @NotNull final JTextComponent c,
@NotNull final boolean isToEndOfLine, final boolean isToStartOfLine,
private Shape paintSelectionEnd(@NotNull final Graphics2D g2d, @NotNull final Rectangle r, final boolean isFirstLine, final boolean isLastLine,
@NotNull final JTextComponent c, final Rectangle posStart, final boolean isSecondLine, final boolean isSecondLastLine,
final Rectangle posEnd) { final boolean selectionStart, final boolean selectionEnd,
if (DEBUG_COLOR) g2d.setColor(Color.GREEN); final Rectangle posStart, final Rectangle posEnd,
final Rectangle r) {
var margin = c.getMargin(); var margin = c.getMargin();
r.width += r.x - margin.left; boolean rounded = isRounded(c);
r.x = margin.left; if (isToEndOfLine) {
if (!c.getComponentOrientation().isLeftToRight() && r.y == posStart.y) { if (DEBUG_COLOR) g2d.setColor(Color.CYAN);
r.width -= posStart.x - margin.left; int start = r.x + r.width;
r.x = posStart.x; 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()) { r.width += w;
if (posEnd.x >= (c.getWidth() - margin.right - arcSize / 2)) { r.x = start;
//Fill to right edge even if corners are not rounded.
r.width = c.getWidth() - margin.right - margin.left;
} }
return r;
} }
if (isRounded(c)) {
boolean roundRightTop = posEnd.x < (posStart.x + 2) && posEnd.y == posStart.y + posStart.height; private void paintRoundRect(@NotNull final Graphics g, @NotNull final Rectangle r, final int arcSize,
boolean roundLeftTop = posEnd.y == posStart.y + posStart.height && posStart.x != margin.left; final boolean leftTop, final boolean rightTop,
final boolean leftBottom, final boolean rightBottom) {
g2d.fillRoundRect(r.x, r.y, r.width, r.height, arcSize, arcSize); int aw = Math.min(arcSize, r.width);
if (!roundLeftTop && !roundRightTop) { int ah = Math.min(arcSize, r.height);
//Optimize draw calls g.fillRoundRect(r.x, r.y, r.width, r.height, aw, ah);
g2d.fillRect(r.x, r.y, r.width, arcSize); if (!leftTop) g.fillRect(r.x, r.y, aw, ah);
} else if (!roundLeftTop) { if (!leftBottom) g.fillRect(r.x, r.y + r.height - ah, aw, ah);
g2d.fillRect(r.x, r.y, arcSize, arcSize); if (!rightTop) g.fillRect(r.x + r.width - aw, r.y, aw, ah);
} else if (!roundRightTop) { if (!rightBottom) g.fillRect(r.x + r.width - aw, r.y + r.height - ah, aw, ah);
g2d.fillRect(r.x + r.width - arcSize, r.y, arcSize, arcSize); }
}
boolean drawCorner = !roundRightTop && r.x + r.width <= c.getWidth() - margin.right - arcSize; private void paintRoundedLeftRight(final Graphics g, final boolean left, final boolean right, final Rectangle r) {
if (drawCorner) { if (right || left) {
paintEndArc(g2d, r); g.fillRoundRect(r.x, r.y, r.width, r.height, arcSize, arcSize);
r.width += 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 { } 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) { 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(); LafManager.install();
final var frame = new JFrame(); final var frame = new JFrame();
frame.setSize(500, 500); frame.setSize(500, 500);
var overlayScroll = new JScrollPane(new JEditorPane() {{ var overlayScroll = new JTextPane() {{
// setEditorKit(new HTMLEditorKit());
setText(TestResources.LOREM_IPSUM.repeat(10)); setText(TestResources.LOREM_IPSUM.repeat(10));
setFont(Font.getFont(Font.MONOSPACED)); setFont(Font.getFont(Font.MONOSPACED));
// setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); // setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
}}); // SimpleAttributeSet attribs = new SimpleAttributeSet();
// StyleConstants.setAlignment(attribs, StyleConstants.ALIGN_RIGHT);
// setParagraphAttributes(attribs, true);
}};
frame.setContentPane(new JPanel(new BorderLayout()) {{ frame.setContentPane(new JPanel(new BorderLayout()) {{
add(overlayScroll, BorderLayout.CENTER); add(overlayScroll, BorderLayout.CENTER);
}}); }});

Loading…
Cancel
Save