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. 375
      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.

375
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 { } else {
// Should only render part of View. alloc = new Rectangle(posOffs0.x, posOffs0.y, posOffs1.x + posOffs1.width - posOffs0.x,
//Start/End parts of selection posOffs1.y + posOffs1.height - posOffs0.y);
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 {
dirtyShape = paintSelectionEnd(g2d, r, c, posStart, posEnd);
}
} }
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,161 +257,81 @@ 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) {
* Selection is contained to one line. r.width -= arcSize;
*/ r.x += arcSize;
if (DEBUG_COLOR) g2d.setColor(Color.BLUE); }
if (isRounded(c)) { if (toEndOfLine) {
g2d.fillRoundRect(r.x, r.y, r.width, r.height, arcSize, arcSize); r.width -= arcSize;
} else {
g2d.fillRect(r.x, r.y, r.width, r.height);
} }
g.fillRect(r.x, r.y, r.width, r.height);
return r; return r;
} }
@Contract("_, _, _, _, _ -> param2") /*
* Selection is contained to one line.
*/
@Contract("_, _, _, _, _ -> param3")
@NotNull @NotNull
private Shape paintSelectionLine(@NotNull final Graphics2D g2d, private Shape paintSelection(final Graphics2D g2d, final JTextComponent c, final Rectangle r,
@NotNull final Rectangle alloc, final boolean selectionStart, final boolean selectionEnd) {
@NotNull final JTextComponent c, if (DEBUG_COLOR) g2d.setColor(Color.BLUE);
@NotNull final Rectangle posStart,
@NotNull final Rectangle posEnd) throws BadLocationException {
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; paintRoundedLeftRight(g2d, selectionStart, selectionEnd, r);
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;
}
} 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) {
return roundedEdges || Boolean.TRUE.equals(c.getClientProperty("JTextComponent.roundedSelection"));
} }
@Contract("_, _, _, _, _, _ -> param2")
@NotNull @NotNull
@Contract("_, _, _, _, _ -> param2")
private Shape paintSelectionStart(@NotNull final Graphics2D g2d, @NotNull final Rectangle r, private Shape paintSelectionStart(@NotNull final Graphics2D g2d, @NotNull final Rectangle r,
@NotNull final JTextComponent c, @NotNull final JTextComponent c,
@NotNull final Rectangle posStart, final Rectangle posEnd) { @NotNull final Rectangle posStart,
final Rectangle posOffs0,
final boolean endBeforeStart) {
if (DEBUG_COLOR) g2d.setColor(Color.RED); if (DEBUG_COLOR) g2d.setColor(Color.RED);
r.width = c.getWidth() - posStart.x - c.getMargin().right;
var margin = c.getMargin(); var margin = c.getMargin();
if (isRounded(c)) { if (isRounded(c)) {
boolean roundLeftBottom = posEnd.x < (posStart.x + 2) && posEnd.y == posStart.y + posStart.height; paintRoundRect(g2d, r, arcSize, true, false, endBeforeStart, false);
boolean roundRightBottom = posEnd.y == posStart.y + posStart.height; boolean drawCorner = posOffs0.equals(posStart) && !endBeforeStart && r.x >= margin.left + arcSize;
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 (drawCorner) { if (drawCorner) {
paintStartArc(g2d, r); paintStartArc(g2d, r);
r.x -= arcSize; r.x -= arcSize;
@ -443,40 +343,29 @@ public class DarkHighlightPainter extends DefaultHighlighter.DefaultHighlightPai
return r; return r;
} }
@Contract("_, _, _, _, _ -> param2") private boolean isRounded(final JTextComponent c) {
return roundedEdges || Boolean.TRUE.equals(c.getClientProperty("JTextComponent.roundedSelection"));
}
@Contract("_, _, _, _, _, _, _, _, _ -> param2")
@NotNull @NotNull
private Shape paintSelectionEnd(@NotNull final Graphics2D g2d, @NotNull final Rectangle r, private Shape paintSelectionEnd(@NotNull final Graphics2D g2d, @NotNull final Rectangle r,
@NotNull final JTextComponent c, final Rectangle posStart, @NotNull final JTextComponent c, final Rectangle posStart,
final Rectangle posEnd) { final boolean isFirstLine, final boolean isSecondLine,
final boolean extendToStart, final boolean extendToEnd,
final boolean endBeforeStart) {
if (DEBUG_COLOR) g2d.setColor(Color.GREEN); if (DEBUG_COLOR) g2d.setColor(Color.GREEN);
var margin = c.getMargin(); var margin = c.getMargin();
r.width += r.x - margin.left; if (r.x + r.width >= c.getWidth() - margin.right - arcSize / 2.0) {
r.x = margin.left; int end = c.getWidth() - margin.right;
if (!c.getComponentOrientation().isLeftToRight() && r.y == posStart.y) { r.width = end - r.x;
r.width -= posStart.x - margin.left;
r.x = posStart.x;
}
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;
}
} }
if (isRounded(c)) { if (isRounded(c)) {
boolean roundRightTop = posEnd.x < (posStart.x + 2) && posEnd.y == posStart.y + posStart.height; boolean roundRightTop = endBeforeStart && !extendToEnd;
boolean roundLeftTop = posEnd.y == posStart.y + posStart.height && posStart.x != margin.left; boolean roundLeftBottom = !isFirstLine && !extendToStart;
boolean roundLeftTop = isSecondLine && !extendToStart && posStart.x >= r.x + arcSize;
g2d.fillRoundRect(r.x, r.y, r.width, r.height, arcSize, arcSize); paintRoundRect(g2d, r, arcSize, roundLeftTop, roundRightTop, roundLeftBottom, !extendToEnd);
if (!roundLeftTop && !roundRightTop) { boolean drawCorner = !extendToEnd && !roundRightTop && r.x + r.width <= c.getWidth() - margin.right - arcSize;
//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) { if (drawCorner) {
paintEndArc(g2d, r); paintEndArc(g2d, r);
r.width += arcSize; r.width += arcSize;
@ -487,6 +376,82 @@ public class DarkHighlightPainter extends DefaultHighlighter.DefaultHighlightPai
return r; return r;
} }
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();
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);
}
r.width += w;
r.x = start;
}
return r;
}
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 {
g.fillRect(r.x, r.y, r.width, r.height);
}
}
private void paintStartArc(@NotNull final Graphics2D g2d, @NotNull final Rectangle r) { private void paintStartArc(@NotNull final Graphics2D g2d, @NotNull final Rectangle r) {
if (DEBUG_COLOR) g2d.setColor(Color.PINK); if (DEBUG_COLOR) g2d.setColor(Color.PINK);

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