4
4
import io .github .humbleui .jwm .KeyModifier ;
5
5
import io .github .humbleui .jwm .MouseCursor ;
6
6
import io .github .humbleui .skija .Canvas ;
7
+ import io .github .humbleui .skija .Matrix33 ;
7
8
import io .github .humbleui .skija .Paint ;
8
9
import io .github .humbleui .skija .paragraph .RectHeightMode ;
9
10
import io .github .humbleui .skija .paragraph .RectWidthMode ;
10
11
import org .jsignal .prop .GeneratePropComponent ;
11
12
import org .jsignal .prop .Prop ;
12
13
import org .jsignal .prop .TransitiveProps ;
13
14
import org .jsignal .rx .Constant ;
15
+ import org .jsignal .rx .Effect ;
14
16
import org .jsignal .rx .Ref ;
15
17
import org .jsignal .rx .Signal ;
16
18
import org .jsignal .std .ez .EzColors ;
17
19
import org .jsignal .std .ez .EzLayout ;
18
20
import org .jsignal .ui .Element ;
21
+ import org .jsignal .ui .MathUtil ;
19
22
import org .jsignal .ui .Node ;
20
23
import org .jsignal .ui .UiWindow ;
24
+ import org .jsignal .ui .event .MouseEvent ;
21
25
import org .jsignal .ui .layout .Layout ;
22
26
import org .jsignal .ui .layout .Layouter ;
23
27
@@ -52,7 +56,10 @@ public static class Transitive {
52
56
private final Signal <Boolean > isFocused = Signal .create (false );
53
57
54
58
private Para para ;
59
+ private final Ref <Node > ref = new Ref <>();
55
60
private final Signal <Optional <Integer >> cursorPosition = Signal .create (Optional .empty ());
61
+ private final Signal <Boolean > mouseDown = Signal .create (false );
62
+ private final Signal <Optional <SelectionRange >> selectionRange = Signal .create (Optional .empty ());
56
63
57
64
@ Override
58
65
protected void onBuild (Transitive transitive ) {
@@ -61,10 +68,21 @@ protected void onBuild(Transitive transitive) {
61
68
62
69
@ Override
63
70
public Element render () {
64
- var ref = new Ref <Node >();
65
-
66
71
var window = UiWindow .context .use ().getWindow ().get ();
67
72
73
+ Effect .create (() -> {
74
+ if (mouseDown .get ()) {
75
+ Effect .create (() -> {
76
+ var pos = MathUtil .apply (
77
+ MathUtil .inverse (para .getRef ().getFullTransform ()),
78
+ UiWindow .context .use ().getMousePosition ()
79
+ );
80
+ var gPos = para .getParagraph ().getGlyphPositionAtCoordinate (pos .getX (), pos .getY ());
81
+ selectionRange .accept (Optional .of (new SelectionRange (selectionRange .get ().get ().start , gPos .getPosition ())));
82
+ });
83
+ }
84
+ });
85
+
68
86
return Node .builder ()
69
87
.ref (ref )
70
88
.listen (List .of (
@@ -79,16 +97,18 @@ public Element render() {
79
97
onBlur (event -> {
80
98
isFocused .accept (false );
81
99
cursorPosition .accept (Optional .empty ());
100
+ selectionRange .accept (Optional .empty ());
101
+ }),
102
+ onMouseDown (event -> {
103
+ var pos = mouseEventToPosition (event , ref .get ());
104
+ cursorPosition .accept (Optional .empty ());
105
+ pos .ifPresent (p -> selectionRange .accept (Optional .of (new SelectionRange (p , p ))));
106
+ mouseDown .accept (true );
82
107
}),
108
+ onMouseUp (event -> mouseDown .accept (false )),
83
109
onMouseClick (event -> {
84
- if (content .get ().isEmpty ()) {
85
- cursorPosition .accept (Optional .of (0 ));
86
- } else {
87
- var pos = event .getPoint ();
88
- var content = ref .get ().getLayout ().getContentRect ();
89
- var x = pos .getX () - content .getLeft ();
90
- var y = pos .getY () - content .getTop ();
91
- cursorPosition .accept (Optional .of (para .getParagraph ().getGlyphPositionAtCoordinate (x , y ).getPosition ()));
110
+ if (selectionRange .get ().isEmpty () || selectionRange .get ().get ().start == selectionRange .get ().get ().end ) {
111
+ cursorPosition .accept (mouseEventToPosition (event , ref .get ()));
92
112
}
93
113
}),
94
114
onKeyDown (event -> {
@@ -143,12 +163,26 @@ public Element render() {
143
163
layout .getPaddingRect ().withRadii (radius ), paint
144
164
);
145
165
}
166
+
167
+ paintSelection (canvas , layout );
146
168
})
147
169
.paintAfter (this ::paintCursor )
148
170
.children (children .apply (para ))
149
171
.build ();
150
172
}
151
173
174
+ private Optional <Integer > mouseEventToPosition (MouseEvent e , Node node ) {
175
+ if (content .get ().isEmpty ()) {
176
+ return Optional .of (0 );
177
+ } else {
178
+ var pos = e .getPoint ();
179
+ var content = node .getLayout ().getContentRect ();
180
+ var x = pos .getX () - content .getLeft ();
181
+ var y = pos .getY () - content .getTop ();
182
+ return Optional .of (para .getParagraph ().getGlyphPositionAtCoordinate (x , y ).getPosition ());
183
+ }
184
+ }
185
+
152
186
private void paintCursor (Canvas canvas , Layout layout ) {
153
187
cursorPosition .get ().ifPresent (pos -> {
154
188
try (var paint = new Paint ()) {
@@ -163,7 +197,7 @@ private void paintCursor(Canvas canvas, Layout layout) {
163
197
return ;
164
198
}
165
199
166
- var p = ignore (() -> para .getParagraph () );
200
+ var p = para .getParagraph ();
167
201
var lineMetrics = p .getLineMetrics ();
168
202
var lastLineMetrics = lineMetrics [lineMetrics .length - 1 ];
169
203
int endPos = (int ) lastLineMetrics .getEndIndex ();
@@ -177,6 +211,40 @@ private void paintCursor(Canvas canvas, Layout layout) {
177
211
});
178
212
}
179
213
214
+ private void paintSelection (Canvas canvas , Layout layout ) {
215
+ selectionRange .get ().ifPresent (range -> {
216
+ if (range .start == range .end ) {
217
+ return ;
218
+ }
219
+
220
+ var p = para .getParagraph ();
221
+ var rects = p .getRectsForRange (range .getMin (), range .getMax (), RectHeightMode .MAX , RectWidthMode .MAX );
222
+ var contentRect = layout .getContentRect ();
223
+ int count = canvas .save ();
224
+ try {
225
+ canvas .concat (Matrix33 .makeTranslate (contentRect .getLeft (), contentRect .getTop ()));
226
+ try (var paint = new Paint ()) {
227
+ paint .setColor (EzColors .BLUE_400 );
228
+ for (var rect : rects ) {
229
+ canvas .drawRect (rect .getRect (), paint );
230
+ }
231
+ }
232
+ } finally {
233
+ canvas .restoreToCount (count );
234
+ }
235
+ });
236
+ }
237
+
238
+ private record SelectionRange (int start , int end ) {
239
+ public int getMin () {
240
+ return Math .min (start , end );
241
+ }
242
+
243
+ public int getMax () {
244
+ return Math .max (start , end );
245
+ }
246
+ }
247
+
180
248
public static String keyToInputString (Key key , boolean lower ) {
181
249
if (lower ) {
182
250
return switch (key ) {
0 commit comments