1use super::{ScrollBar, ScrollMsg};
9use kas::event::components::{TextInput, TextInputAction};
10use kas::event::{Command, CursorIcon, FocusSource, Scroll, ScrollDelta};
11use kas::geom::Vec2;
12use kas::prelude::*;
13use kas::text::format::{EditableText, FormattableText};
14use kas::text::{NotReady, SelectionHelper};
15use kas::theme::{Text, TextClass};
16
17impl_scope! {
18 #[widget{
23 cursor_icon = CursorIcon::Text;
24 }]
25 pub struct ScrollText<A, T: Default + FormattableText + 'static> {
26 core: widget_core!(),
27 view_offset: Offset,
28 text: Text<T>,
29 text_fn: Box<dyn Fn(&ConfigCx, &A) -> T>,
30 text_size: Size,
31 selection: SelectionHelper,
32 has_sel_focus: bool,
33 input_handler: TextInput,
34 #[widget(&())]
35 bar: ScrollBar<kas::dir::Down>,
36 }
37
38 impl Layout for Self {
39 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
40 let mut rules = sizer.text_rules(&mut self.text, axis);
41 let _ = self.bar.size_rules(sizer.re(), axis);
42 if axis.is_vertical() {
43 rules.reduce_min_to(sizer.text_line_height(&self.text) * 4);
44 }
45 rules
46 }
47
48 fn set_rect(&mut self, cx: &mut ConfigCx, mut rect: Rect, hints: AlignHints) {
49 self.core.rect = rect;
50 cx.text_set_size(&mut self.text, rect.size, hints.complete_default());
51 self.text_size = Vec2::from(self.text.bounding_box().unwrap().1).cast_ceil();
52
53 let max_offset = self.max_scroll_offset();
54 self.view_offset = self.view_offset.min(max_offset);
55
56 let w = cx.size_cx().scroll_bar_width().min(rect.size.0);
57 rect.pos.0 += rect.size.0 - w;
58 rect.size.0 = w;
59 self.bar.set_rect(cx, rect, AlignHints::NONE);
60 let _ = self.bar.set_limits(max_offset.1, rect.size.1);
61 self.bar.set_value(cx, self.view_offset.1);
62 }
63
64 fn find_id(&mut self, coord: Coord) -> Option<Id> {
65 if !self.rect().contains(coord) {
66 return None;
67 }
68
69 self.bar.find_id(coord).or_else(|| Some(self.id()))
70 }
71
72 fn draw(&mut self, mut draw: DrawCx) {
73 let rect = Rect::new(self.rect().pos, self.text_size);
74 draw.with_clip_region(self.rect(), self.view_offset, |mut draw| {
75 if self.selection.is_empty() {
76 draw.text(rect, &self.text);
77 } else {
78 draw.text_selected(rect, &self.text, self.selection.range());
82 }
83 });
84 draw.with_pass(|mut draw| {
85 draw.recurse(&mut self.bar);
86 });
87 }
88 }
89
90 impl Self {
91 #[inline]
93 pub fn new(text_fn: impl Fn(&ConfigCx, &A) -> T + 'static) -> Self {
94 ScrollText {
95 core: Default::default(),
96 view_offset: Default::default(),
97 text: Text::new(T::default(), TextClass::LabelScroll),
98 text_fn: Box::new(text_fn),
99 text_size: Size::ZERO,
100 selection: SelectionHelper::new(0, 0),
101 has_sel_focus: false,
102 input_handler: Default::default(),
103 bar: ScrollBar::new().with_invisible(true),
104 }
105 }
106
107 fn set_edit_pos_from_coord(&mut self, cx: &mut EventCx, coord: Coord) {
108 let rel_pos = (coord - self.rect().pos + self.view_offset).cast();
109 if let Ok(pos) = self.text.text_index_nearest(rel_pos) {
110 if pos != self.selection.edit_pos() {
111 self.selection.set_edit_pos(pos);
112 self.set_view_offset_from_edit_pos(cx, pos);
113 self.bar.set_value(cx, self.view_offset.1);
114 cx.redraw(self);
115 }
116 }
117 }
118
119 fn set_primary(&self, cx: &mut EventCx) {
120 if self.has_sel_focus && !self.selection.is_empty() && cx.has_primary() {
121 let range = self.selection.range();
122 cx.set_primary(String::from(&self.text.as_str()[range]));
123 }
124 }
125
126 fn pan_delta(&mut self, cx: &mut EventCx, mut delta: Offset) -> IsUsed {
128 let new_offset = (self.view_offset - delta)
129 .min(self.max_scroll_offset())
130 .max(Offset::ZERO);
131 if new_offset != self.view_offset {
132 delta -= self.view_offset - new_offset;
133 self.set_offset(cx, new_offset);
134 }
135
136 cx.set_scroll(if delta == Offset::ZERO {
137 Scroll::Scrolled
138 } else {
139 Scroll::Offset(delta)
140 });
141 Used
142 }
143
144 fn set_view_offset_from_edit_pos(&mut self, cx: &mut EventCx, edit_pos: usize) {
148 if let Some(marker) = self
149 .text
150 .text_glyph_pos(edit_pos)
151 .ok()
152 .and_then(|mut m| m.next_back())
153 {
154 let bounds = Vec2::from(self.text.bounds());
155 let min_x = marker.pos.0 - bounds.0;
156 let min_y = marker.pos.1 - marker.descent - bounds.1;
157 let max_x = marker.pos.0;
158 let max_y = marker.pos.1 - marker.ascent;
159 let min = Offset(min_x.cast_ceil(), min_y.cast_ceil());
160 let max = Offset(max_x.cast_floor(), max_y.cast_floor());
161
162 let max = max.min(self.max_scroll_offset());
163
164 let new_offset = self.view_offset.max(min).min(max);
165 if new_offset != self.view_offset {
166 self.view_offset = new_offset;
167 cx.set_scroll(Scroll::Scrolled);
168 }
169 }
170 }
171
172 fn set_offset(&mut self, cx: &mut EventState, offset: Offset) {
174 self.view_offset = offset;
175 self.bar.set_value(cx, offset.1);
177 }
178 }
179
180 impl HasStr for Self {
181 fn get_str(&self) -> &str {
182 self.text.as_str()
183 }
184 }
185
186 impl HasString for Self
187 where
188 T: EditableText,
189 {
190 fn set_string(&mut self, string: String) -> Action {
191 if string == self.text.as_str() {
192 return Action::empty();
193 }
194
195 self.text.set_string(string);
196 match self.text.prepare() {
197 Err(NotReady) => Action::empty(),
198 Ok(_) => Action::SET_RECT,
199 }
200 }
201 }
202
203 impl Events for Self {
204 type Data = A;
205
206 fn configure(&mut self, cx: &mut ConfigCx) {
207 cx.text_configure(&mut self.text);
208 }
209
210 fn update(&mut self, cx: &mut ConfigCx, data: &A) {
211 let text = (self.text_fn)(cx, data);
212 if text.as_str() == self.text.as_str() {
213 return;
216 }
217 self.text.set_text(text);
218 let action = match self.text.prepare() {
219 Err(NotReady) => Action::empty(),
220 Ok(_) => Action::SET_RECT,
221 };
222 debug_assert!(!action.is_empty(), "update before configure");
223 cx.action(self, action);
224 }
225
226 fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
227 match event {
228 Event::Command(cmd, _) => match cmd {
229 Command::Escape | Command::Deselect if !self.selection.is_empty() => {
230 self.selection.set_empty();
231 cx.redraw(self);
232 Used
233 }
234 Command::SelectAll => {
235 self.selection.set_sel_pos(0);
236 self.selection.set_edit_pos(self.text.str_len());
237 self.set_primary(cx);
238 cx.redraw(self);
239 Used
240 }
241 Command::Cut | Command::Copy => {
242 let range = self.selection.range();
243 cx.set_clipboard((self.text.as_str()[range]).to_string());
244 Used
245 }
246 _ => Unused,
248 },
249 Event::SelFocus(source) => {
250 self.has_sel_focus = true;
251 if source == FocusSource::Pointer {
252 self.set_primary(cx);
253 }
254 Used
255 }
256 Event::LostSelFocus => {
257 self.has_sel_focus = false;
258 self.selection.set_empty();
259 cx.redraw(self);
260 Used
261 }
262 Event::Scroll(delta) => {
263 let delta2 = match delta {
264 ScrollDelta::LineDelta(x, y) => cx.config().event().scroll_distance((x, y)),
265 ScrollDelta::PixelDelta(coord) => coord,
266 };
267 self.pan_delta(cx, delta2)
268 }
269 event => match self.input_handler.handle(cx, self.id(), event) {
270 TextInputAction::None => Used,
271 TextInputAction::Unused => Unused,
272 TextInputAction::Pan(delta) => self.pan_delta(cx, delta),
273 TextInputAction::Focus { coord, action } => {
274 if let Some(coord) = coord {
275 self.set_edit_pos_from_coord(cx, coord);
276 }
277 self.selection.action(&self.text, action);
278
279 if self.has_sel_focus {
280 self.set_primary(cx);
281 } else {
282 cx.request_sel_focus(self.id(), FocusSource::Pointer);
283 }
284 Used
285 }
286 },
287 }
288 }
289
290 fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
291 if let Some(ScrollMsg(y)) = cx.try_pop() {
292 let y = y.clamp(0, self.max_scroll_offset().1);
293 self.view_offset.1 = y;
294 cx.redraw(self);
295 }
296 }
297 }
298
299 impl Scrollable for Self {
300 fn scroll_axes(&self, size: Size) -> (bool, bool) {
301 let max = self.max_scroll_offset();
302 (max.0 > size.0, max.1 > size.1)
303 }
304
305 fn max_scroll_offset(&self) -> Offset {
306 let text_size = Offset::conv(self.text_size);
307 let self_size = Offset::conv(self.rect().size);
308 (text_size - self_size).max(Offset::ZERO)
309 }
310
311 fn scroll_offset(&self) -> Offset {
312 self.view_offset
313 }
314
315 fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset {
316 let new_offset = offset.min(self.max_scroll_offset()).max(Offset::ZERO);
317 if new_offset != self.view_offset {
318 self.set_offset(cx, new_offset);
319 }
321 new_offset
322 }
323 }
324}