1use super::{ScrollBar, ScrollMsg};
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]
30 #[layout(self.text)]
31 pub struct SelectableText<A, T: FormattableText + 'static> {
32 core: widget_core!(),
33 text: Text<T>,
34 text_fn: Option<Box<dyn Fn(&ConfigCx, &A) -> T>>,
35 selection: SelectionHelper,
36 has_sel_focus: bool,
37 input_handler: TextInput,
38 }
39
40 impl Layout for Self {
41 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
42 let mut rules = kas::MacroDefinedLayout::size_rules(self, sizer.re(), axis);
43 if axis.is_vertical() {
44 rules.reduce_min_to(sizer.dpem().cast_ceil());
45 }
46 rules
47 }
48
49 fn draw(&self, mut draw: DrawCx) {
50 self.draw_with_offset(draw, self.rect(), Offset::ZERO);
51 }
52 }
53
54 impl Tile for Self {
55 fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
56 Role::TextLabel {
57 text: self.text.as_str(),
58 cursor: self.selection.edit_index(),
59 sel_index: self.selection.sel_index(),
60 }
61 }
62 }
63
64 impl<T: FormattableText + 'static> SelectableText<(), T> {
65 #[inline]
69 pub fn new(text: T) -> Self {
70 SelectableText {
71 core: Default::default(),
72 text: Text::new(text, TextClass::LabelScroll),
73 text_fn: None,
74 selection: SelectionHelper::new(0, 0),
75 has_sel_focus: false,
76 input_handler: Default::default(),
77 }
78 }
79
80 #[inline]
84 pub fn with_fn<A>(
85 self,
86 text_fn: impl Fn(&ConfigCx, &A) -> T + 'static,
87 ) -> SelectableText<A, T> {
88 SelectableText {
89 core: self.core,
90 text: self.text,
91 text_fn: Some(Box::new(text_fn)),
92 selection: self.selection,
93 has_sel_focus: self.has_sel_focus,
94 input_handler: self.input_handler,
95 }
96 }
97 }
98
99 impl Self {
100 #[inline]
104 pub fn new_fn(text_fn: impl Fn(&ConfigCx, &A) -> T + 'static) -> Self
105 where
106 T: Default,
107 {
108 SelectableText::<(), T>::new(T::default()).with_fn(text_fn)
109 }
110
111 pub fn set_text(&mut self, text: T) -> bool {
116 self.text.set_text(text);
117 if !self.text.prepare() {
118 return false;
119 }
120
121 self.selection.set_max_len(self.text.str_len());
122 true
123 }
124
125 fn set_cursor_from_coord(&mut self, cx: &mut EventCx, coord: Coord) {
126 let rel_pos = (coord - self.rect().pos).cast();
127 if let Ok(index) = self.text.text_index_nearest(rel_pos) {
128 if index != self.selection.edit_index() {
129 self.selection.set_edit_index(index);
130 self.set_view_offset_from_cursor(cx, index);
131 cx.redraw(self);
132 }
133 }
134 }
135
136 fn set_primary(&self, cx: &mut EventCx) {
137 if self.has_sel_focus && !self.selection.is_empty() && cx.has_primary() {
138 let range = self.selection.range();
139 cx.set_primary(String::from(&self.text.as_str()[range]));
140 }
141 }
142
143 fn set_view_offset_from_cursor(&mut self, cx: &mut EventCx, cursor: usize) {
147 if let Some(marker) = self
148 .text
149 .text_glyph_pos(cursor)
150 .ok()
151 .and_then(|mut m| m.next_back())
152 {
153 let y0 = (marker.pos.1 - marker.ascent).cast_floor();
154 let pos = Coord(marker.pos.0.cast_nearest(), y0);
155 let size = Size(0, i32::conv_ceil(marker.pos.1 - marker.descent) - y0);
156 cx.set_scroll(Scroll::Rect(Rect { pos, size }));
157 }
158 }
159
160 #[inline]
162 pub fn as_str(&self) -> &str {
163 self.text.as_str()
164 }
165
166 #[inline]
170 pub fn typeset_size(&self) -> Size {
171 let mut size = self.rect().size;
172 if let Ok((tl, br)) = self.text.bounding_box() {
173 size.1 = size.1.max((br.1 - tl.1).cast_ceil());
174 }
175 size
176 }
177
178 pub fn draw_with_offset(&self, mut draw: DrawCx, rect: Rect, offset: Offset) {
184 let pos = self.rect().pos - offset;
185
186 if self.selection.is_empty() {
187 draw.text_pos(pos, rect, &self.text);
188 } else {
189 draw.text_selected(pos, rect, &self.text, self.selection.range());
190 }
191 }
192 }
193
194 impl SelectableText<(), String> {
195 #[inline]
197 pub fn set_string(&mut self, cx: &mut EventState, string: String) {
198 if self.text.set_string(string) {
199 self.text.prepare();
200 cx.action(self, Action::SET_RECT);
201 }
202 }
203 }
204
205 impl Events for Self {
206 type Data = A;
207
208 #[inline]
209 fn mouse_over_icon(&self) -> Option<CursorIcon> {
210 Some(CursorIcon::Text)
211 }
212
213 fn configure(&mut self, cx: &mut ConfigCx) {
214 cx.text_configure(&mut self.text);
215 }
216
217 fn update(&mut self, cx: &mut ConfigCx, data: &A) {
218 if let Some(method) = self.text_fn.as_ref() {
219 let text = method(cx, data);
220 if text.as_str() == self.text.as_str() {
221 return;
224 }
225 self.text.set_text(text);
226 self.text.prepare();
227 cx.action(self, Action::SET_RECT);
228 }
229 }
230
231 fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
232 match event {
233 Event::Command(cmd, _) => match cmd {
234 Command::Escape | Command::Deselect if !self.selection.is_empty() => {
235 self.selection.set_empty();
236 cx.redraw(self);
237 Used
238 }
239 Command::SelectAll => {
240 self.selection.set_sel_index(0);
241 self.selection.set_edit_index(self.text.str_len());
242 self.set_primary(cx);
243 cx.redraw(self);
244 Used
245 }
246 Command::Cut | Command::Copy => {
247 let range = self.selection.range();
248 cx.set_clipboard((self.text.as_str()[range]).to_string());
249 Used
250 }
251 _ => Unused,
252 },
253 Event::SelFocus(source) => {
254 self.has_sel_focus = true;
255 if source == FocusSource::Pointer {
256 self.set_primary(cx);
257 }
258 Used
259 }
260 Event::LostSelFocus => {
261 self.has_sel_focus = false;
262 self.selection.set_empty();
263 cx.redraw(self);
264 Used
265 }
266 event => match self.input_handler.handle(cx, self.id(), event) {
267 TextInputAction::Used | TextInputAction::Finish => Used,
268 TextInputAction::Unused => Unused,
269 TextInputAction::Focus { coord, action } => {
270 self.set_cursor_from_coord(cx, coord);
271 self.selection.action(&self.text, action);
272
273 if self.has_sel_focus {
274 self.set_primary(cx);
275 } else {
276 cx.request_sel_focus(self.id(), FocusSource::Pointer);
277 }
278 Used
279 }
280 },
281 }
282 }
283 }
284}
285
286pub type SelectableLabel<T> = SelectableText<(), T>;
291
292#[impl_self]
293mod ScrollText {
294 #[widget]
306 pub struct ScrollText<A, T: FormattableText + 'static> {
307 core: widget_core!(),
308 scroll: ScrollComponent,
309 #[widget]
310 label: SelectableText<A, T>,
311 #[widget = &()]
312 vert_bar: ScrollBar<kas::dir::Down>,
313 }
314
315 impl Layout for Self {
316 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
317 let mut rules = self.label.size_rules(sizer.re(), axis);
318 let _ = self.vert_bar.size_rules(sizer.re(), axis);
319 if axis.is_vertical() {
320 rules.reduce_min_to((sizer.dpem() * 4.0).cast_ceil());
321 }
322 rules.with_stretch(Stretch::Low)
323 }
324
325 fn set_rect(&mut self, cx: &mut ConfigCx, mut rect: Rect, hints: AlignHints) {
326 widget_set_rect!(rect);
327 self.label.set_rect(cx, rect, hints);
328
329 let _ = self
330 .scroll
331 .set_sizes(self.rect().size, self.label.typeset_size());
332
333 let w = cx.size_cx().scroll_bar_width().min(rect.size.0);
334 rect.pos.0 += rect.size.0 - w;
335 rect.size.0 = w;
336 self.vert_bar.set_rect(cx, rect, AlignHints::NONE);
337 self.vert_bar
338 .set_limits(cx, self.scroll.max_offset().1, rect.size.1);
339 self.vert_bar.set_value(cx, self.scroll.offset().1);
340 }
341
342 fn draw(&self, mut draw: DrawCx) {
343 self.label
344 .draw_with_offset(draw.re(), self.rect(), self.scroll.offset());
345
346 if self.vert_bar.currently_visible(draw.ev_state()) {
349 draw.with_pass(|draw| self.vert_bar.draw(draw));
350 }
351 }
352 }
353
354 impl Tile for Self {
355 fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
356 Role::ScrollRegion {
357 offset: self.scroll_offset(),
358 max_offset: self.max_scroll_offset(),
359 }
360 }
361
362 fn translation(&self, index: usize) -> Offset {
363 if index == widget_index!(self.label) {
364 self.scroll.offset()
365 } else {
366 Offset::ZERO
367 }
368 }
369
370 fn probe(&self, coord: Coord) -> Id {
371 self.vert_bar
372 .try_probe(coord)
373 .unwrap_or_else(|| self.label.id())
374 }
375 }
376
377 impl<T: FormattableText + 'static> ScrollText<(), T> {
378 #[inline]
382 pub fn new(text: T) -> Self {
383 ScrollText {
384 core: Default::default(),
385 scroll: Default::default(),
386 label: SelectableText::new(text),
387 vert_bar: ScrollBar::new().with_invisible(true),
388 }
389 }
390
391 #[inline]
395 pub fn with_fn<A>(
396 self,
397 text_fn: impl Fn(&ConfigCx, &A) -> T + 'static,
398 ) -> ScrollText<A, T> {
399 ScrollText {
400 core: self.core,
401 scroll: self.scroll,
402 label: self.label.with_fn(text_fn),
403 vert_bar: self.vert_bar,
404 }
405 }
406 }
407
408 impl Self {
409 #[inline]
413 pub fn new_fn(text_fn: impl Fn(&ConfigCx, &A) -> T + 'static) -> Self
414 where
415 T: Default,
416 {
417 ScrollText::<(), T>::new(T::default()).with_fn(text_fn)
418 }
419
420 pub fn set_text(&mut self, cx: &mut EventState, text: T) {
425 if self.label.set_text(text) {
426 self.vert_bar
427 .set_limits(cx, self.scroll.max_offset().1, self.rect().size.1);
428
429 cx.redraw(self);
430 }
431 }
432
433 pub fn as_str(&self) -> &str {
435 self.label.as_str()
436 }
437 }
438
439 impl ScrollText<(), String> {
440 pub fn set_string(&mut self, cx: &mut EventState, string: String) {
442 self.label.set_string(cx, string);
443 }
444 }
445
446 impl Events for Self {
447 type Data = A;
448
449 #[inline]
450 fn mouse_over_icon(&self) -> Option<CursorIcon> {
451 Some(CursorIcon::Text)
452 }
453
454 fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
455 let is_used = self
456 .scroll
457 .scroll_by_event(cx, event, self.id(), self.rect());
458 self.vert_bar.set_value(cx, self.scroll.offset().1);
459 is_used
460 }
461
462 fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
463 if cx.last_child() == Some(widget_index![self.vert_bar])
464 && let Some(ScrollMsg(y)) = cx.try_pop()
465 {
466 let offset = Offset(self.scroll.offset().0, y);
467 let action = self.scroll.set_offset(offset);
468 self.vert_bar.set_value(cx, self.scroll.offset().1);
469 cx.action(self, action);
470 } else if let Some(kas::messages::SetScrollOffset(offset)) = cx.try_pop() {
471 self.set_scroll_offset(cx, offset);
472 }
473 }
474
475 fn handle_scroll(&mut self, cx: &mut EventCx, _: &Self::Data, scroll: Scroll) {
476 self.scroll.scroll(cx, self.id(), self.rect(), scroll);
477 self.vert_bar.set_value(cx, self.scroll.offset().1);
478 }
479 }
480
481 impl Scrollable for Self {
482 fn content_size(&self) -> Size {
483 self.label.rect().size
484 }
485
486 fn max_scroll_offset(&self) -> Offset {
487 self.scroll.max_offset()
488 }
489
490 fn scroll_offset(&self) -> Offset {
491 self.scroll.offset()
492 }
493
494 fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset {
495 let action = self.scroll.set_offset(offset);
496 let offset = self.scroll.offset();
497 if !action.is_empty() {
498 cx.action(&self, action);
499 self.vert_bar.set_value(cx, offset.1);
500 }
501 offset
502 }
503 }
504}
505
506pub type ScrollLabel<T> = ScrollText<(), T>;