1use super::{ScrollBar, ScrollBarMsg};
9use kas::event::components::{ScrollComponent, TextInput, TextInputAction};
10use kas::event::{CursorIcon, FocusSource, Scroll};
11use kas::prelude::*;
12use kas::text::SelectionHelper;
13use kas::text::format::FormattableText;
14use kas::theme::{Text, TextClass};
15
16#[impl_self]
17mod SelectableText {
18 #[widget]
33 #[layout(self.text)]
34 pub struct SelectableText<A, T: FormattableText + 'static> {
35 core: widget_core!(),
36 text: Text<T>,
37 text_fn: Option<Box<dyn Fn(&ConfigCx, &A) -> T>>,
38 selection: SelectionHelper,
39 has_sel_focus: bool,
40 input_handler: TextInput,
41 }
42
43 impl Layout for Self {
44 fn size_rules(&mut self, cx: &mut SizeCx, axis: AxisInfo) -> SizeRules {
45 let mut rules = kas::MacroDefinedLayout::size_rules(self, cx, axis);
46 if axis.is_vertical()
47 && let Some(width) = axis.other()
48 {
49 let height = self
50 .text
51 .measure_height(width.cast(), std::num::NonZero::new(3));
52 rules.reduce_min_to(height.cast_ceil());
53 }
54 rules
55 }
56 }
57
58 impl Viewport for Self {
59 #[inline]
60 fn content_size(&self) -> Size {
61 if let Ok((tl, br)) = self.text.bounding_box() {
62 (br - tl).cast_ceil()
63 } else {
64 Size::ZERO
65 }
66 }
67
68 fn draw_with_offset(&self, mut draw: DrawCx, rect: Rect, offset: Offset) {
69 let pos = self.rect().pos - offset;
70
71 if self.selection.is_empty() {
72 draw.text_with_position(pos, rect, &self.text);
73 } else {
74 draw.text_with_selection(pos, rect, &self.text, self.selection.range());
75 }
76 }
77 }
78
79 impl Tile for Self {
80 fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
81 Role::TextLabel {
82 text: self.text.as_str(),
83 cursor: self.selection.edit_index(),
84 sel_index: self.selection.sel_index(),
85 }
86 }
87 }
88
89 impl<T: FormattableText + 'static> SelectableText<(), T> {
90 #[inline]
94 pub fn new(text: T) -> Self {
95 SelectableText {
96 core: Default::default(),
97 text: Text::new(text, TextClass::Standard, true),
98 text_fn: None,
99 selection: SelectionHelper::default(),
100 has_sel_focus: false,
101 input_handler: Default::default(),
102 }
103 }
104
105 #[inline]
109 pub fn with_fn<A>(
110 self,
111 text_fn: impl Fn(&ConfigCx, &A) -> T + 'static,
112 ) -> SelectableText<A, T> {
113 SelectableText {
114 core: self.core,
115 text: self.text,
116 text_fn: Some(Box::new(text_fn)),
117 selection: self.selection,
118 has_sel_focus: self.has_sel_focus,
119 input_handler: self.input_handler,
120 }
121 }
122 }
123
124 impl Self {
125 #[inline]
129 pub fn new_fn(text_fn: impl Fn(&ConfigCx, &A) -> T + 'static) -> Self
130 where
131 T: Default,
132 {
133 SelectableText::<(), T>::new(T::default()).with_fn(text_fn)
134 }
135
136 #[inline]
138 pub fn as_str(&self) -> &str {
139 self.text.as_str()
140 }
141
142 pub fn set_text(&mut self, text: T) -> bool {
149 self.text.set_text(text);
150 if !self.text.prepare() {
151 return false;
152 }
153
154 self.selection.set_max_len(self.text.str_len());
155 true
156 }
157
158 #[inline]
160 pub fn class(&self) -> TextClass {
161 self.text.class()
162 }
163
164 #[inline]
168 pub fn set_class(&mut self, class: TextClass) {
169 self.text.set_class(class);
170 }
171
172 #[inline]
176 pub fn with_class(mut self, class: TextClass) -> Self {
177 self.text.set_class(class);
178 self
179 }
180
181 fn set_cursor_from_coord(&mut self, cx: &mut EventCx, coord: Coord) {
182 let rel_pos = (coord - self.rect().pos).cast();
183 if let Ok(index) = self.text.text_index_nearest(rel_pos) {
184 if index != self.selection.edit_index() {
185 self.selection.set_edit_index(index);
186 self.set_view_offset_from_cursor(cx, index);
187 cx.redraw();
188 }
189 }
190 }
191
192 fn set_primary(&self, cx: &mut EventCx) {
193 if self.has_sel_focus && !self.selection.is_empty() && cx.has_primary() {
194 let range = self.selection.range();
195 cx.set_primary(String::from(&self.text.as_str()[range]));
196 }
197 }
198
199 fn set_view_offset_from_cursor(&mut self, cx: &mut EventCx, cursor: usize) {
203 if let Some(marker) = self
204 .text
205 .text_glyph_pos(cursor)
206 .ok()
207 .and_then(|mut m| m.next_back())
208 {
209 let y0 = (marker.pos.1 - marker.ascent).cast_floor();
210 let pos = Coord(marker.pos.0.cast_nearest(), y0);
211 let size = Size(0, i32::conv_ceil(marker.pos.1 - marker.descent) - y0);
212 cx.set_scroll(Scroll::Rect(Rect { pos, size }));
213 }
214 }
215 }
216
217 impl SelectableText<(), String> {
218 #[inline]
222 pub fn set_string(&mut self, string: String) -> bool {
223 if self.text.set_string(string) {
224 self.text.prepare();
225 true
226 } else {
227 false
228 }
229 }
230 }
231
232 impl Events for Self {
233 type Data = A;
234
235 #[inline]
236 fn mouse_over_icon(&self) -> Option<CursorIcon> {
237 Some(CursorIcon::Text)
238 }
239
240 fn configure(&mut self, cx: &mut ConfigCx) {
241 self.text.configure(&mut cx.size_cx());
242 }
243
244 fn update(&mut self, cx: &mut ConfigCx, data: &A) {
245 if let Some(method) = self.text_fn.as_ref() {
246 if self.set_text(method(cx, data)) {
247 cx.resize();
248 }
249 }
250 }
251
252 fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
253 match event {
254 Event::Command(cmd, _) => match cmd {
255 Command::Escape | Command::Deselect if !self.selection.is_empty() => {
256 self.selection.set_empty();
257 cx.redraw();
258 Used
259 }
260 Command::SelectAll => {
261 self.selection.set_sel_index(0);
262 self.selection.set_edit_index(self.text.str_len());
263 self.set_primary(cx);
264 cx.redraw();
265 Used
266 }
267 Command::Cut | Command::Copy => {
268 let range = self.selection.range();
269 cx.set_clipboard((self.text.as_str()[range]).to_string());
270 Used
271 }
272 _ => Unused,
273 },
274 Event::SelFocus(source) => {
275 self.has_sel_focus = true;
276 if source == FocusSource::Pointer {
277 self.set_primary(cx);
278 }
279 Used
280 }
281 Event::LostSelFocus => {
282 self.has_sel_focus = false;
283 self.selection.set_empty();
284 cx.redraw();
285 Used
286 }
287 event => match self.input_handler.handle(cx, self.id(), event) {
288 TextInputAction::Used => Used,
289 TextInputAction::Unused => Unused,
290 TextInputAction::PressStart {
291 coord,
292 clear,
293 repeats,
294 } => {
295 self.set_cursor_from_coord(cx, coord);
296 self.selection.set_anchor(clear);
297 if repeats > 1 {
298 self.selection.expand(&self.text, repeats >= 3);
299 }
300
301 if !self.has_sel_focus {
302 cx.request_sel_focus(self.id(), FocusSource::Pointer);
303 }
304 Used
305 }
306 TextInputAction::PressMove { coord, repeats } => {
307 self.set_cursor_from_coord(cx, coord);
308 if repeats > 1 {
309 self.selection.expand(&self.text, repeats >= 3);
310 }
311 Used
312 }
313 TextInputAction::PressEnd { .. } => {
314 if self.has_sel_focus {
315 self.set_primary(cx);
316 }
317 Used
318 }
319 },
320 }
321 }
322 }
323}
324
325pub type SelectableLabel<T> = SelectableText<(), T>;
330
331#[impl_self]
332mod ScrollText {
333 #[widget]
348 pub struct ScrollText<A, T: FormattableText + 'static> {
349 core: widget_core!(),
350 scroll: ScrollComponent,
351 #[widget]
353 text: SelectableText<A, T>,
354 #[widget = &()]
355 vert_bar: ScrollBar<kas::dir::Down>,
356 }
357
358 impl Layout for Self {
359 fn size_rules(&mut self, cx: &mut SizeCx, axis: AxisInfo) -> SizeRules {
360 let mut rules = self.text.size_rules(cx, axis);
361 let _ = self.vert_bar.size_rules(cx, axis);
362 if axis.is_vertical() {
363 let dpem = cx.dpem(self.text.text.class());
364 rules.reduce_min_to((dpem * 4.0).cast_ceil());
365 }
366 rules.with_stretch(Stretch::Low)
367 }
368
369 fn set_rect(&mut self, cx: &mut SizeCx, mut rect: Rect, hints: AlignHints) {
370 self.core.set_rect(rect);
371 self.text.set_rect(cx, rect, hints);
372
373 let w = cx.scroll_bar_width().min(rect.size.0);
374 rect.pos.0 += rect.size.0 - w;
375 rect.size.0 = w;
376 self.vert_bar.set_rect(cx, rect, AlignHints::NONE);
377
378 self.update_content_size(cx);
379 }
380
381 fn draw(&self, mut draw: DrawCx) {
382 self.text
383 .draw_with_offset(draw.re(), self.rect(), self.scroll.offset());
384
385 if self.vert_bar.currently_visible(draw.ev_state()) {
388 draw.with_pass(|draw| self.vert_bar.draw(draw));
389 }
390 }
391 }
392
393 impl Tile for Self {
394 fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
395 Role::ScrollRegion {
396 offset: self.scroll.offset(),
397 max_offset: self.scroll.max_offset(),
398 }
399 }
400
401 fn translation(&self, index: usize) -> Offset {
402 if index == widget_index!(self.text) {
403 self.scroll.offset()
404 } else {
405 Offset::ZERO
406 }
407 }
408 }
409
410 impl<T: FormattableText + 'static> ScrollText<(), T> {
411 #[inline]
415 pub fn new(text: T) -> Self {
416 ScrollText {
417 core: Default::default(),
418 scroll: Default::default(),
419 text: SelectableText::new(text),
420 vert_bar: ScrollBar::new().with_invisible(true),
421 }
422 }
423
424 #[inline]
428 pub fn with_fn<A>(
429 self,
430 text_fn: impl Fn(&ConfigCx, &A) -> T + 'static,
431 ) -> ScrollText<A, T> {
432 ScrollText {
433 core: self.core,
434 scroll: self.scroll,
435 text: self.text.with_fn(text_fn),
436 vert_bar: self.vert_bar,
437 }
438 }
439 }
440
441 impl Self {
442 #[inline]
446 pub fn new_fn(text_fn: impl Fn(&ConfigCx, &A) -> T + 'static) -> Self
447 where
448 T: Default,
449 {
450 ScrollText::<(), T>::new(T::default()).with_fn(text_fn)
451 }
452
453 pub fn as_str(&self) -> &str {
455 self.text.as_str()
456 }
457
458 pub fn set_text(&mut self, cx: &mut EventState, text: T) {
463 if self.text.set_text(text) {
464 self.update_content_size(cx);
465 cx.redraw(self);
466 }
467 }
468
469 #[inline]
471 pub fn class(&self) -> TextClass {
472 self.text.class()
473 }
474
475 #[inline]
479 pub fn set_class(&mut self, class: TextClass) {
480 self.text.set_class(class);
481 }
482
483 #[inline]
487 pub fn with_class(mut self, class: TextClass) -> Self {
488 self.text.set_class(class);
489 self
490 }
491
492 fn update_content_size(&mut self, cx: &mut EventState) {
493 if !self.core.status.is_sized() {
494 return;
495 }
496 let size = self.rect().size;
497 let _ = self.scroll.set_sizes(size, self.text.content_size());
498 self.vert_bar
499 .set_limits(cx, self.scroll.max_offset().1, size.1);
500 self.vert_bar.set_value(cx, self.scroll.offset().1);
501 }
502 }
503
504 impl ScrollText<(), String> {
505 pub fn set_string(&mut self, cx: &mut EventState, string: String) {
507 if self.text.set_string(string) {
508 self.update_content_size(cx);
509 }
510 }
511 }
512
513 impl Events for Self {
514 type Data = A;
515
516 fn probe(&self, coord: Coord) -> Id {
517 self.vert_bar
518 .try_probe(coord)
519 .unwrap_or_else(|| self.text.id())
520 }
521
522 #[inline]
523 fn mouse_over_icon(&self) -> Option<CursorIcon> {
524 Some(CursorIcon::Text)
525 }
526
527 fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
528 let is_used = self
529 .scroll
530 .scroll_by_event(cx, event, self.id(), self.rect());
531 self.vert_bar.set_value(cx, self.scroll.offset().1);
532 is_used
533 }
534
535 fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
536 let action = if cx.last_child() == Some(widget_index![self.vert_bar])
537 && let Some(ScrollBarMsg(y)) = cx.try_pop()
538 {
539 let offset = Offset(self.scroll.offset().0, y);
540 self.scroll.set_offset(offset)
541 } else if let Some(kas::messages::SetScrollOffset(offset)) = cx.try_pop() {
542 self.scroll.set_offset(offset)
543 } else {
544 return;
545 };
546
547 if let Some(moved) = action {
548 cx.action_moved(moved);
549 self.vert_bar.set_value(cx, self.scroll.offset().1);
550 }
551 }
552
553 fn handle_resize(&mut self, cx: &mut ConfigCx, _: &Self::Data) -> Option<ActionResize> {
554 let size = self.text.rect().size;
555 let axis = AxisInfo::new(false, Some(size.1));
556 let mut resize = self.text.size_rules(&mut cx.size_cx(), axis).min_size() > size.0;
557 let axis = AxisInfo::new(true, Some(size.0));
558 resize |= self.text.size_rules(&mut cx.size_cx(), axis).min_size() > size.1;
559 self.text
560 .set_rect(&mut cx.size_cx(), self.text.rect(), Default::default());
561 self.update_content_size(cx);
562 resize.then_some(ActionResize)
563 }
564
565 fn handle_scroll(&mut self, cx: &mut EventCx, _: &Self::Data, scroll: Scroll) {
566 self.scroll.scroll(cx, self.id(), self.rect(), scroll);
567 self.vert_bar.set_value(cx, self.scroll.offset().1);
568 }
569 }
570}
571
572pub type ScrollLabel<T> = ScrollText<(), T>;