Created
December 8, 2016 16:30
-
-
Save nodamushi/8ff17b42fe43b25bb6dbf966393b9485 to your computer and use it in GitHub Desktop.
JavaFX9でTextAreaに行番号がついたTextAreaを作ってみた
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package nodamushi.jfx; | |
import javafx.scene.control.TextArea; | |
import javafx.scene.control.skin.TextInputControlSkin; | |
import static javafx.collections.FXCollections.observableArrayList; | |
import static javafx.collections.FXCollections.unmodifiableObservableList; | |
import java.util.ArrayList; | |
import java.util.List; | |
import javafx.collections.ObservableList; | |
import javafx.geometry.Rectangle2D; | |
/** | |
* | |
* @author nodamushi | |
*/ | |
public class MyTextArea extends TextArea{ | |
public static class Line{ | |
int begin; | |
int end; | |
int line; | |
public int getBegin(){return begin;} | |
public int getEnd(){return end;} | |
public int getLine(){return line;} | |
public boolean contains(int index){return begin <= index && index < end;} | |
private boolean update(int begin,int end,int line){ | |
if( begin == this.begin && end == this.end && line == this.line){ | |
return false; | |
} | |
this.begin = begin; | |
this.end = end; | |
this.line = line; | |
return true; | |
} | |
} | |
public static class YRange{ | |
public final double start; | |
public final double end; | |
public YRange(double s,double e){start = s;end = e;} | |
public double getStart(){return start;} | |
public double getEnd(){return end;} | |
} | |
@Override | |
protected MyTextAreaSkin createDefaultSkin() { | |
return new MyTextAreaSkin(this); | |
} | |
private ObservableList<Line> linesData ,unmodify; | |
public ObservableList<Line> getLines(){ | |
if(unmodify == null){ | |
linesData = observableArrayList(); | |
unmodify = unmodifiableObservableList(linesData); | |
textProperty().addListener(o->updateText()); | |
updateText(); | |
} | |
return unmodify; | |
} | |
public YRange getLineYRange(Line line){ | |
TextInputControlSkin<?> skin = (TextInputControlSkin<?>)getSkin(); | |
Rectangle2D r1 = skin.getCharacterBounds(line.begin); | |
Rectangle2D r2 = skin.getCharacterBounds(line.end); | |
double begin = Math.min(r1.getMinY(),r2.getMinY()); | |
double end = Math.max(r1.getMaxY(),r2.getMaxY()); | |
return new YRange(begin,end); | |
} | |
public YRange getLineYRange(int line){ | |
List<Line> lines = getLines(); | |
if(lines.size() < line){ | |
throw new IndexOutOfBoundsException(line); | |
} | |
Line l = lines.get(line); | |
return getLineYRange(l); | |
} | |
private void updateText(){ | |
String text = getText(); | |
List<Line> cloneData = this.cloneData = new ArrayList<>(linesData); | |
int line = 0; | |
int begin = 0; | |
boolean update=false; | |
int len = text.length(); | |
int i=0; | |
for(;i<len;i++){ | |
switch(text.charAt(i)){ | |
case '\r': | |
if(i+1 < len && text.charAt(i+1)=='\n'){ | |
i++; | |
} | |
case '\n': | |
update|=updateLine(begin, i, line); | |
line++; | |
begin = i+1; | |
break; | |
} | |
} | |
if(i!=begin){ | |
update|=updateLine(begin, i, line); | |
} | |
this.cloneData = null; | |
if(line+1 < cloneData.size()){ | |
update = true; | |
cloneData.subList(line+1, cloneData.size()).clear(); | |
} | |
if(linesData.isEmpty()){ | |
Line l =new Line(); | |
l.begin = 0;l.end = 0;l.line = 0; | |
linesData.add(l); | |
} | |
if(update){ | |
linesData.setAll(cloneData); | |
} | |
} | |
private List<Line> cloneData; | |
private boolean updateLine(int begin,int end,int line){ | |
if(cloneData.size() < line+1){ | |
Line l =new Line(); | |
l.begin = begin;l.end = end;l.line = line; | |
cloneData.add(l); | |
return true; | |
}else{ | |
return cloneData.get(line).update(begin, end, line); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package nodamushi.jfx; | |
import java.util.List; | |
import javafx.collections.ObservableList; | |
import javafx.geometry.Bounds; | |
import javafx.scene.Group; | |
import javafx.scene.Node; | |
import javafx.scene.control.skin.TextAreaSkin; | |
import javafx.scene.text.Text; | |
/** | |
* | |
* @author nodamushi | |
*/ | |
public class MyTextAreaSkin extends TextAreaSkin{ | |
private final MyTextArea textarea; | |
private final Group lineNumbers; | |
private final Text dummy; | |
public MyTextAreaSkin(MyTextArea area){ | |
super(area); | |
textarea = area; | |
lineNumbers = new Group(); | |
lineNumbers.setManaged(false); | |
dummy = new Text(); | |
getChildren().addAll(lineNumbers); | |
textarea.setWrapText(true); | |
textarea.scrollTopProperty().addListener(o->textarea.requestLayout()); | |
} | |
private double prefLineNumbersWidth(double h){ | |
int n =textarea.getLines().size(); | |
StringBuilder sb = new StringBuilder(); | |
do{ | |
sb.append('9'); | |
n/=10; | |
}while(n != 0); | |
dummy.setText(sb.toString()); | |
ObservableList<Node> l =lineNumbers.getChildren(); | |
l.add(dummy); | |
Bounds b = dummy.getBoundsInLocal(); | |
l.remove(l.size()-1); | |
return b.getWidth(); | |
} | |
@Override | |
protected void layoutChildren(double x, double y, double w, double h) { | |
double lnw = prefLineNumbersWidth(h); | |
double taw = w - lnw - 6; | |
super.layoutChildren(lnw+6+x, y, taw, h); | |
double zeroY = -getCharacterBounds(0).getMinY(); | |
int insertPoint = getInsertionPoint(0,zeroY); | |
int lastInsertPoint = getInsertionPoint(0,zeroY + h); | |
if(lastInsertPoint < 0){ | |
lastInsertPoint = textarea.getLength(); | |
} | |
List<MyTextArea.Line> lines = textarea.getLines(); | |
ObservableList<Node> nums = lineNumbers.getChildren(); | |
int index =0,nlen = nums.size(); | |
double minY = -100000000; | |
for(int l=0,end = lines.size();l<end;l++){ | |
MyTextArea.Line line = lines.get(l); | |
if(line.end <insertPoint)continue; | |
if(line.begin > lastInsertPoint)break; | |
MyTextArea.YRange ly = textarea.getLineYRange(l); | |
if(ly.end < 0)continue; | |
if(ly.start > y + h || minY > y+h){ | |
break; | |
} | |
Text text; | |
if(index < nlen){ | |
text = (Text)nums.get(index); | |
}else{ | |
text = new Text(); | |
text.setManaged(false); | |
nums.add(text); | |
} | |
index++; | |
text.setFont(textarea.getFont()); | |
text.setText(Integer.toString(l+1)); | |
double nextY = ly.start+3; | |
double yy = Math.max(nextY, minY); | |
text.relocate(0, yy); | |
minY = Math.max(text.getBoundsInParent().getMaxY() ,ly.end); | |
} | |
if(index < nlen){ | |
nums.remove(index, nlen); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package nodamushi.jfx; | |
import javafx.application.Application; | |
import javafx.scene.Scene; | |
import javafx.stage.Stage; | |
/** | |
* | |
* @author nodamushi | |
*/ | |
public class TextAreaApplication extends Application{ | |
public static void main(String[] args) { | |
launch(args); | |
} | |
@Override | |
public void start(Stage stage) throws Exception { | |
MyTextArea area = new MyTextArea(); | |
area.setWrapText(true); | |
area.setPrefSize(500, 500); | |
Scene scene = new Scene(area); | |
stage.setScene(scene); | |
stage.show(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment