1use super::*;
54use crate::draw_context::DrawCtx;
55use crate::scrollbar::{scrollbar_base, scrollbar_drag_delta, scrollbar_max_scroll, scrollbar_thumb, ScrollAxis};
56use crate::text_layout::build_text_lines;
57use std::cell::RefCell;
58
59macro_rules! widget_layout {
60 ($(#[$meta:meta])* $name:ident, $state:ty, $builder:expr) => {
61 $(#[$meta])*
62 pub fn $name(&mut self, state: &mut $state) -> ResourceState {
63 let rect = self.next_widget_rect(state);
64 ($builder)(self, state, rect)
65 }
66 };
67 ($(#[$meta:meta])* $name:ident, $state:ty) => {
68 $(#[$meta])*
69 pub fn $name(&mut self, state: &mut $state) -> ResourceState {
70 self.handle_widget(state, None)
71 }
72 };
73}
74
75pub struct CustomRenderArgs {
77 pub content_area: Rect<i32>,
79 pub view: Rect<i32>, pub mouse_event: MouseEvent,
83 pub scroll_delta: Option<Vec2i>,
85 pub widget_opt: WidgetOption,
87 pub behaviour_opt: WidgetBehaviourOption,
89 pub key_mods: KeyMode,
91 pub key_codes: KeyCode,
93 pub text_input: String,
95}
96
97#[derive(Copy, Clone, Debug, PartialEq, Eq)]
99pub enum TextWrap {
100 None,
102 Word,
104}
105
106pub(crate) enum Command {
108 Clip {
110 rect: Recti,
112 },
113 Recti {
115 rect: Recti,
117 color: Color,
119 },
120 Text {
122 font: FontId,
124 pos: Vec2i,
126 color: Color,
128 text: String,
130 },
131 Icon {
133 rect: Recti,
135 id: IconId,
137 color: Color,
139 },
140 Image {
142 rect: Recti,
144 image: Image,
146 color: Color,
148 },
149 SlotRedraw {
151 rect: Recti,
153 id: SlotId,
155 color: Color,
157 payload: Rc<dyn Fn(usize, usize) -> Color4b>,
159 },
160 CustomRender(CustomRenderArgs, Box<dyn FnMut(Dimensioni, &CustomRenderArgs)>),
162 None,
164}
165
166impl Default for Command {
167 fn default() -> Self {
168 Command::None
169 }
170}
171
172pub struct Container {
174 pub(crate) atlas: AtlasHandle,
175 pub(crate) style: Rc<Style>,
177 pub(crate) name: String,
179 pub(crate) rect: Recti,
181 pub(crate) body: Recti,
183 pub(crate) content_size: Vec2i,
185 pub(crate) scroll: Vec2i,
187 pub(crate) zindex: i32,
189 pub(crate) command_list: Vec<Command>,
191 pub(crate) clip_stack: Vec<Recti>,
193 pub(crate) layout: LayoutManager,
194 pub(crate) hover: Option<WidgetId>,
196 pub(crate) focus: Option<WidgetId>,
198 pub(crate) updated_focus: bool,
200 pub(crate) scrollbar_y_state: Internal,
202 pub(crate) scrollbar_x_state: Internal,
204 pub(crate) input: Rc<RefCell<Input>>,
206 input_snapshot: Option<Rc<InputSnapshot>>,
208 pub(crate) in_hover_root: bool,
210 pub(crate) popup_just_opened: bool,
212 pending_scroll: Option<Vec2i>,
213 scroll_enabled: bool,
215
216 panels: Vec<ContainerHandle>,
217}
218
219impl Container {
220 pub(crate) fn new(name: &str, atlas: AtlasHandle, style: Rc<Style>, input: Rc<RefCell<Input>>) -> Self {
221 Self {
222 name: name.to_string(),
223 style,
224 atlas: atlas,
225 rect: Recti::default(),
226 body: Recti::default(),
227 content_size: Vec2i::default(),
228 scroll: Vec2i::default(),
229 zindex: 0,
230 command_list: Vec::default(),
231 clip_stack: Vec::default(),
232 hover: None,
233 focus: None,
234 updated_focus: false,
235 layout: LayoutManager::default(),
236 scrollbar_y_state: Internal::new("!scrollbary"),
237 scrollbar_x_state: Internal::new("!scrollbarx"),
238 popup_just_opened: false,
239 in_hover_root: false,
240 input: input,
241 input_snapshot: None,
242 pending_scroll: None,
243 scroll_enabled: true,
244
245 panels: Default::default(),
246 }
247 }
248
249 pub(crate) fn reset(&mut self) {
250 self.hover = None;
251 self.focus = None;
252 self.updated_focus = false;
253 self.in_hover_root = false;
254 self.input_snapshot = None;
255 self.pending_scroll = None;
256 self.scroll_enabled = true;
257 }
258
259 pub(crate) fn prepare(&mut self) {
260 self.command_list.clear();
261 assert!(self.clip_stack.len() == 0);
262 self.panels.clear();
263 self.input_snapshot = None;
264 self.pending_scroll = None;
265 self.scroll_enabled = true;
266 }
267
268 pub(crate) fn seed_pending_scroll(&mut self, delta: Option<Vec2i>) {
269 self.pending_scroll = delta;
270 }
271
272 #[inline(never)]
273 pub(crate) fn render<R: Renderer>(&mut self, canvas: &mut Canvas<R>) {
274 for command in self.command_list.drain(0..) {
275 match command {
276 Command::Text { text, pos, color, font } => {
277 canvas.draw_chars(font, &text, pos, color);
278 }
279 Command::Recti { rect, color } => {
280 canvas.draw_rect(rect, color);
281 }
282 Command::Icon { id, rect, color } => {
283 canvas.draw_icon(id, rect, color);
284 }
285 Command::Clip { rect } => {
286 canvas.set_clip_rect(rect);
287 }
288 Command::Image { rect, image, color } => {
289 canvas.draw_image(image, rect, color);
290 }
291 Command::SlotRedraw { rect, id, color, payload } => {
292 canvas.draw_slot_with_function(id, rect, color, payload.clone());
293 }
294 Command::CustomRender(mut cra, mut f) => {
295 canvas.flush();
296 let prev_clip = canvas.current_clip_rect();
297 let merged_clip = match prev_clip.intersect(&cra.view) {
298 Some(rect) => rect,
299 None => Recti::new(cra.content_area.x, cra.content_area.y, 0, 0),
300 };
301 canvas.set_clip_rect(merged_clip);
302 cra.view = merged_clip;
303 (*f)(canvas.current_dimension(), &cra);
304 canvas.flush();
305 canvas.set_clip_rect(prev_clip);
306 }
307 Command::None => (),
308 }
309 }
310
311 for ap in &mut self.panels {
312 ap.render(canvas)
313 }
314 }
315
316 fn draw_ctx(&mut self) -> DrawCtx<'_> {
317 DrawCtx::new(&mut self.command_list, &mut self.clip_stack, self.style.as_ref(), &self.atlas)
318 }
319
320 pub fn push_clip_rect(&mut self, rect: Recti) {
322 let mut draw = self.draw_ctx();
323 draw.push_clip_rect(rect);
324 }
325
326 pub fn pop_clip_rect(&mut self) {
328 let mut draw = self.draw_ctx();
329 draw.pop_clip_rect();
330 }
331
332 pub fn get_clip_rect(&mut self) -> Recti {
334 self.draw_ctx().current_clip_rect()
335 }
336
337 pub fn check_clip(&mut self, r: Recti) -> Clip {
339 self.draw_ctx().check_clip(r)
340 }
341
342 pub fn set_clip(&mut self, rect: Recti) {
344 let mut draw = self.draw_ctx();
345 draw.set_clip(rect);
346 }
347
348 pub fn set_focus(&mut self, widget_id: Option<WidgetId>) {
350 self.focus = widget_id;
351 self.updated_focus = true;
352 }
353
354 pub fn draw_rect(&mut self, rect: Recti, color: Color) {
356 let mut draw = self.draw_ctx();
357 draw.draw_rect(rect, color);
358 }
359
360 pub fn draw_box(&mut self, r: Recti, color: Color) {
362 let mut draw = self.draw_ctx();
363 draw.draw_box(r, color);
364 }
365
366 pub fn draw_text(&mut self, font: FontId, str: &str, pos: Vec2i, color: Color) {
368 let mut draw = self.draw_ctx();
369 draw.draw_text(font, str, pos, color);
370 }
371
372 pub fn draw_icon(&mut self, id: IconId, rect: Recti, color: Color) {
374 let mut draw = self.draw_ctx();
375 draw.draw_icon(id, rect, color);
376 }
377
378 pub fn draw_slot(&mut self, id: SlotId, rect: Recti, color: Color) {
380 let mut draw = self.draw_ctx();
381 draw.push_image(Image::Slot(id), rect, color);
382 }
383
384 pub fn draw_slot_with_function(&mut self, id: SlotId, rect: Recti, color: Color, f: Rc<dyn Fn(usize, usize) -> Color4b>) {
386 let mut draw = self.draw_ctx();
387 draw.draw_slot_with_function(id, rect, color, f);
388 }
389
390 #[inline(never)]
391 pub fn text(&mut self, text: &str) {
393 self.text_with_wrap(text, TextWrap::None);
394 }
395
396 #[inline(never)]
397 pub fn text_with_wrap(&mut self, text: &str, wrap: TextWrap) {
401 if text.is_empty() {
402 return;
403 }
404 let style = self.style.as_ref();
405 let font = style.font;
406 let color = style.colors[ControlColor::Text as usize];
407 let line_height = self.atlas.get_font_height(font) as i32;
408 let baseline = self.atlas.get_font_baseline(font);
409 let saved_spacing = self.layout.style.spacing;
410 self.layout.style.spacing = 0;
411 self.column(|ui| {
412 ui.layout.row(&[SizePolicy::Remainder(0)], SizePolicy::Fixed(line_height));
413 let first_rect = ui.layout.next();
414 let max_width = first_rect.width;
415 let mut lines = build_text_lines(text, wrap, max_width, font, &ui.atlas);
416 if text.ends_with('\n') {
417 if let Some(last) = lines.last() {
418 if last.start == text.len() && last.end == text.len() {
419 lines.pop();
420 }
421 }
422 }
423 for (idx, line) in lines.iter().enumerate() {
424 let r = if idx == 0 { first_rect } else { ui.layout.next() };
425 let line_top = Self::baseline_aligned_top(r, line_height, baseline);
426 let slice = &text[line.start..line.end];
427 if !slice.is_empty() {
428 ui.draw_text(font, slice, vec2(r.x, line_top), color);
429 }
430 }
431 });
432 self.layout.style.spacing = saved_spacing;
433 }
434
435 pub fn draw_frame(&mut self, rect: Recti, colorid: ControlColor) {
437 let mut draw = self.draw_ctx();
438 draw.draw_frame(rect, colorid);
439 }
440
441 pub fn draw_widget_frame(&mut self, widget_id: WidgetId, rect: Recti, colorid: ControlColor, opt: WidgetOption) {
443 let focused = self.focus == Some(widget_id);
444 let hovered = self.hover == Some(widget_id);
445 let mut draw = self.draw_ctx();
446 draw.draw_widget_frame(focused, hovered, rect, colorid, opt);
447 }
448
449 pub fn draw_container_frame(&mut self, widget_id: WidgetId, rect: Recti, mut colorid: ControlColor, opt: ContainerOption) {
451 if opt.has_no_frame() {
452 return;
453 }
454
455 if self.focus == Some(widget_id) {
456 colorid.focus()
457 } else if self.hover == Some(widget_id) {
458 colorid.hover()
459 }
460 let mut draw = self.draw_ctx();
461 draw.draw_frame(rect, colorid);
462 }
463
464 #[inline(never)]
465 pub fn draw_control_text(&mut self, str: &str, rect: Recti, colorid: ControlColor, opt: WidgetOption) {
467 let mut draw = self.draw_ctx();
468 draw.draw_control_text(str, rect, colorid, opt);
469 }
470
471 pub fn mouse_over(&mut self, rect: Recti, in_hover_root: bool) -> bool {
473 let clip_rect = self.get_clip_rect();
474 rect.contains(&self.input.borrow().mouse_pos) && clip_rect.contains(&self.input.borrow().mouse_pos) && in_hover_root
475 }
476
477 fn update_control_with_opts(&mut self, widget_id: WidgetId, rect: Recti, opt: WidgetOption, bopt: WidgetBehaviourOption) -> ControlState {
478 let in_hover_root = self.in_hover_root;
479 let mouseover = self.mouse_over(rect, in_hover_root);
480 if self.focus == Some(widget_id) {
481 self.updated_focus = true;
483 }
484 if opt.is_not_interactive() {
485 return ControlState::default();
486 }
487 if mouseover && self.input.borrow().mouse_down.is_none() {
488 self.hover = Some(widget_id);
489 }
490 if self.focus == Some(widget_id) {
491 if !self.input.borrow().mouse_pressed.is_none() && !mouseover {
492 self.set_focus(None);
493 }
494 if self.input.borrow().mouse_down.is_none() && !opt.is_holding_focus() {
495 self.set_focus(None);
496 }
497 }
498 if self.hover == Some(widget_id) {
499 if !self.input.borrow().mouse_pressed.is_none() {
500 self.set_focus(Some(widget_id));
501 } else if !mouseover {
502 self.hover = None;
503 }
504 }
505
506 let mut scroll = None;
507 if bopt.is_grab_scroll() && self.hover == Some(widget_id) {
508 if let Some(delta) = self.pending_scroll {
509 if delta.x != 0 || delta.y != 0 {
510 self.pending_scroll = None;
511 scroll = Some(delta);
512 }
513 }
514 }
515
516 if self.focus == Some(widget_id) {
517 let mouse_pos = self.input.borrow().mouse_pos;
518 let origin = vec2(self.body.x, self.body.y);
519 self.input.borrow_mut().rel_mouse_pos = mouse_pos - origin;
520 }
521
522 let input = self.input.borrow();
523 let focused = self.focus == Some(widget_id);
524 let hovered = self.hover == Some(widget_id);
525 let clicked = focused && input.mouse_pressed.is_left();
526 let active = focused && input.mouse_down.is_left();
527 drop(input);
528
529 ControlState {
530 hovered,
531 focused,
532 clicked,
533 active,
534 scroll_delta: scroll,
535 }
536 }
537
538 #[inline(never)]
539 pub fn update_control<W: Widget>(&mut self, widget_id: WidgetId, rect: Recti, state: &W) -> ControlState {
541 self.update_control_with_opts(widget_id, rect, *state.widget_opt(), *state.behaviour_opt())
542 }
543
544 fn snapshot_input(&mut self) -> Rc<InputSnapshot> {
545 if let Some(snapshot) = &self.input_snapshot {
546 return snapshot.clone();
547 }
548
549 let input = self.input.borrow();
550 let snapshot = Rc::new(InputSnapshot {
551 mouse_pos: input.mouse_pos,
552 mouse_delta: input.mouse_delta,
553 mouse_down: input.mouse_down,
554 mouse_pressed: input.mouse_pressed,
555 key_mods: input.key_down,
556 key_pressed: input.key_pressed,
557 key_codes: input.key_code_down,
558 key_code_pressed: input.key_code_pressed,
559 text_input: input.input_text.clone(),
560 });
561 self.input_snapshot = Some(snapshot.clone());
562 snapshot
563 }
564
565 pub(crate) fn widget_ctx(&mut self, widget_id: WidgetId, rect: Recti, input: Option<Rc<InputSnapshot>>) -> WidgetCtx<'_> {
566 WidgetCtx::new(
567 widget_id,
568 rect,
569 &mut self.command_list,
570 &mut self.clip_stack,
571 self.style.as_ref(),
572 &self.atlas,
573 &mut self.focus,
574 &mut self.updated_focus,
575 self.in_hover_root,
576 input,
577 )
578 }
579
580 fn run_widget<W: Widget>(
581 &mut self,
582 state: &mut W,
583 rect: Recti,
584 input: Option<Rc<InputSnapshot>>,
585 opt: WidgetOption,
586 bopt: WidgetBehaviourOption,
587 ) -> (ControlState, ResourceState) {
588 let widget_id = widget_id_of(state);
589 let control = self.update_control_with_opts(widget_id, rect, opt, bopt);
590 let mut ctx = self.widget_ctx(widget_id, rect, input);
591 let res = state.handle(&mut ctx, &control);
592 (control, res)
593 }
594
595 fn next_widget_rect<W: Widget>(&mut self, state: &W) -> Recti {
596 let body = self.layout.current_body();
598 let avail = Dimensioni::new(body.width.max(0), body.height.max(0));
599 let preferred = state.preferred_size(self.style.as_ref(), &self.atlas, avail);
600 self.layout.next_with_preferred(preferred)
601 }
602
603 fn handle_widget<W: Widget>(&mut self, state: &mut W, input: Option<Rc<InputSnapshot>>) -> ResourceState {
604 let rect = self.next_widget_rect(state);
605 let opt = *state.widget_opt();
606 let bopt = *state.behaviour_opt();
607 let (_, res) = self.run_widget(state, rect, input, opt, bopt);
608 res
609 }
610
611 fn handle_widget_in_rect<W: Widget>(
612 &mut self,
613 state: &mut W,
614 rect: Recti,
615 input: Option<Rc<InputSnapshot>>,
616 opt: WidgetOption,
617 bopt: WidgetBehaviourOption,
618 ) -> ResourceState {
619 let (_, res) = self.run_widget(state, rect, input, opt, bopt);
620 res
621 }
622
623 pub fn finish(&mut self) {
625 if !self.updated_focus {
626 self.focus = None;
627 }
628 self.updated_focus = false;
629 }
630
631 pub fn rect(&self) -> Recti {
633 self.rect
634 }
635
636 pub fn set_rect(&mut self, rect: Recti) {
638 self.rect = rect;
639 }
640
641 pub fn body(&self) -> Recti {
643 self.body
644 }
645
646 pub fn scroll(&self) -> Vec2i {
648 self.scroll
649 }
650
651 pub fn set_scroll(&mut self, scroll: Vec2i) {
653 self.scroll = scroll;
654 }
655
656 pub fn content_size(&self) -> Vec2i {
658 self.content_size
659 }
660
661 fn node_scope<F: FnOnce(&mut Self)>(&mut self, state: &mut Node, indent: bool, f: F) -> NodeStateValue {
662 self.layout.row(&[SizePolicy::Remainder(0)], SizePolicy::Auto);
663 let r = self.next_widget_rect(state);
664 let opt = *state.widget_opt();
665 let bopt = *state.behaviour_opt();
666 let _ = self.handle_widget_in_rect(state, r, None, opt, bopt);
667 if state.state.is_expanded() {
668 if indent {
669 let indent_size = self.style.as_ref().indent;
670 self.layout.adjust_indent(indent_size);
671 f(self);
672 self.layout.adjust_indent(-indent_size);
673 } else {
674 f(self);
675 }
676 }
677 state.state
678 }
679
680 pub fn header<F: FnOnce(&mut Self)>(&mut self, state: &mut Node, f: F) -> NodeStateValue {
682 self.node_scope(state, false, f)
683 }
684
685 pub fn treenode<F: FnOnce(&mut Self)>(&mut self, state: &mut Node, f: F) -> NodeStateValue {
687 self.node_scope(state, true, f)
688 }
689
690 fn clamp(x: i32, a: i32, b: i32) -> i32 {
691 min(b, max(a, x))
692 }
693
694 fn baseline_aligned_top(rect: Recti, line_height: i32, baseline: i32) -> i32 {
696 if rect.height >= line_height {
697 return rect.y + (rect.height - line_height) / 2;
698 }
699
700 let baseline_center = rect.y + rect.height / 2;
701 let min_top = rect.y + rect.height - line_height;
702 let max_top = rect.y;
703 Self::clamp(baseline_center - baseline, min_top, max_top)
704 }
705
706 fn vertical_text_padding(padding: i32) -> i32 {
707 max(1, padding / 2)
708 }
709
710 pub(crate) fn consume_pending_scroll(&mut self) {
711 if !self.scroll_enabled {
712 return;
713 }
714 let delta = match self.pending_scroll {
715 Some(delta) if delta.x != 0 || delta.y != 0 => delta,
716 _ => return,
717 };
718
719 let mut consumed = false;
720 let mut scroll = self.scroll;
721 let mut content_size = self.content_size;
722 let padding = self.style.as_ref().padding * 2;
723 content_size.x += padding;
724 content_size.y += padding;
725 let body = self.body;
726
727 let maxscroll_y = content_size.y - body.height;
728 if delta.y != 0 && maxscroll_y > 0 && body.height > 0 {
729 let new_scroll = Self::clamp(scroll.y + delta.y, 0, maxscroll_y);
730 if new_scroll != scroll.y {
731 scroll.y = new_scroll;
732 consumed = true;
733 }
734 }
735
736 let maxscroll_x = content_size.x - body.width;
737 if delta.x != 0 && maxscroll_x > 0 && body.width > 0 {
738 let new_scroll = Self::clamp(scroll.x + delta.x, 0, maxscroll_x);
739 if new_scroll != scroll.x {
740 scroll.x = new_scroll;
741 consumed = true;
742 }
743 }
744
745 if consumed {
746 self.scroll = scroll;
747 self.pending_scroll = None;
748 }
749 }
750
751 #[inline(never)]
752 fn scrollbars(&mut self, body: &mut Recti) {
753 let (scrollbar_size, padding, thumb_size) = {
754 let style = self.style.as_ref();
755 (style.scrollbar_size, style.padding, style.thumb_size)
756 };
757 let sz = scrollbar_size;
758 let mut cs: Vec2i = self.content_size;
759 cs.x += padding * 2;
760 cs.y += padding * 2;
761 let base_body = *body;
762 self.push_clip_rect(body.clone());
763 if cs.y > base_body.height {
764 body.width -= sz;
765 }
766 if cs.x > base_body.width {
767 body.height -= sz;
768 }
769 let body = *body;
770 let maxscroll = scrollbar_max_scroll(cs.y, body.height);
771 if maxscroll > 0 && body.height > 0 {
772 let scrollbar_y_id = widget_id_of(&self.scrollbar_y_state);
773 let base = scrollbar_base(ScrollAxis::Vertical, body, scrollbar_size);
774 let control = self.update_control_with_opts(scrollbar_y_id, base, self.scrollbar_y_state.opt, self.scrollbar_y_state.bopt);
775 {
776 let mut ctx = WidgetCtx::new(
777 scrollbar_y_id,
778 base,
779 &mut self.command_list,
780 &mut self.clip_stack,
781 self.style.as_ref(),
782 &self.atlas,
783 &mut self.focus,
784 &mut self.updated_focus,
785 self.in_hover_root,
786 None,
787 );
788 let _ = self.scrollbar_y_state.handle(&mut ctx, &control);
789 }
790 if control.active {
791 let delta = scrollbar_drag_delta(ScrollAxis::Vertical, self.input.borrow().mouse_delta, cs.y, base);
792 self.scroll.y += delta;
793 }
794 self.scroll.y = Self::clamp(self.scroll.y, 0, maxscroll);
795 self.draw_frame(base, ControlColor::ScrollBase);
796 let thumb = scrollbar_thumb(ScrollAxis::Vertical, base, body.height, cs.y, self.scroll.y, thumb_size);
797 self.draw_frame(thumb, ControlColor::ScrollThumb);
798 } else {
799 self.scroll.y = 0;
800 }
801 let maxscroll_0 = scrollbar_max_scroll(cs.x, body.width);
802 if maxscroll_0 > 0 && body.width > 0 {
803 let scrollbar_x_id = widget_id_of(&self.scrollbar_x_state);
804 let base_0 = scrollbar_base(ScrollAxis::Horizontal, body, scrollbar_size);
805 let control = self.update_control_with_opts(scrollbar_x_id, base_0, self.scrollbar_x_state.opt, self.scrollbar_x_state.bopt);
806 {
807 let mut ctx = WidgetCtx::new(
808 scrollbar_x_id,
809 base_0,
810 &mut self.command_list,
811 &mut self.clip_stack,
812 self.style.as_ref(),
813 &self.atlas,
814 &mut self.focus,
815 &mut self.updated_focus,
816 self.in_hover_root,
817 None,
818 );
819 let _ = self.scrollbar_x_state.handle(&mut ctx, &control);
820 }
821 if control.active {
822 let delta = scrollbar_drag_delta(ScrollAxis::Horizontal, self.input.borrow().mouse_delta, cs.x, base_0);
823 self.scroll.x += delta;
824 }
825 self.scroll.x = Self::clamp(self.scroll.x, 0, maxscroll_0);
826 self.draw_frame(base_0, ControlColor::ScrollBase);
827 let thumb_0 = scrollbar_thumb(ScrollAxis::Horizontal, base_0, body.width, cs.x, self.scroll.x, thumb_size);
828 self.draw_frame(thumb_0, ControlColor::ScrollThumb);
829 } else {
830 self.scroll.x = 0;
831 }
832 self.pop_clip_rect();
833 }
834
835 pub fn push_container_body(&mut self, body: Recti, _opt: ContainerOption, bopt: WidgetBehaviourOption) {
837 let mut body = body;
838 self.scroll_enabled = !bopt.is_no_scroll();
839 if self.scroll_enabled {
840 self.scrollbars(&mut body);
841 }
842 let (layout_padding, style_padding, font, style_clone) = {
843 let style = self.style.as_ref();
844 (-style.padding, style.padding, style.font, style.clone())
845 };
846 let scroll = self.scroll;
847 self.layout.reset(expand_rect(body, layout_padding), scroll);
848 self.layout.style = style_clone;
849 let font_height = self.atlas.get_font_height(font) as i32;
850 let vertical_pad = Self::vertical_text_padding(style_padding);
851 let icon_height = self.atlas.get_icon_size(EXPAND_DOWN_ICON).height;
852 let default_height = max(font_height + vertical_pad * 2, icon_height);
853 self.layout.set_default_cell_height(default_height);
854 self.body = body;
855 }
856
857 fn pop_panel(&mut self, panel: &mut ContainerHandle) {
858 let layout_body = panel.inner().layout.current_body();
859 let layout_max = panel.inner().layout.current_max();
860 let container = &mut panel.inner_mut();
861
862 match layout_max {
863 None => (),
864 Some(lm) => container.content_size = Vec2i::new(lm.x - layout_body.x, lm.y - layout_body.y),
865 }
866
867 container.layout.pop_scope();
868 }
869
870 #[inline(never)]
871 fn begin_panel(&mut self, panel: &mut ContainerHandle, opt: ContainerOption, bopt: WidgetBehaviourOption) {
872 let rect = self.layout.next();
873 let container = &mut panel.inner_mut();
874 container.prepare();
875 container.style = self.style.clone();
876
877 container.rect = rect;
878 if !opt.has_no_frame() {
879 self.draw_frame(rect, ControlColor::PanelBG);
880 }
881
882 container.in_hover_root = self.in_hover_root;
883 if self.pending_scroll.is_some() && self.mouse_over(rect, self.in_hover_root) {
884 container.pending_scroll = self.pending_scroll.take();
885 }
886 container.push_container_body(rect, opt, bopt);
887 let clip_rect = container.body;
888 container.push_clip_rect(clip_rect);
889 }
890
891 fn end_panel(&mut self, panel: &mut ContainerHandle) {
892 panel.inner_mut().pop_clip_rect();
893 self.pop_panel(panel);
894 {
895 let mut inner = panel.inner_mut();
896 inner.consume_pending_scroll();
897 let pending = inner.pending_scroll.take();
898 if self.pending_scroll.is_none() {
899 self.pending_scroll = pending;
900 }
901 }
902 self.panels.push(panel.clone())
903 }
904
905 pub fn panel<F: FnOnce(&mut ContainerHandle)>(&mut self, panel: &mut ContainerHandle, opt: ContainerOption, bopt: WidgetBehaviourOption, f: F) {
907 self.begin_panel(panel, opt, bopt);
908
909 f(panel);
911
912 self.end_panel(panel);
913 }
914
915 pub fn with_row<F: FnOnce(&mut Self)>(&mut self, widths: &[SizePolicy], height: SizePolicy, f: F) {
917 let snapshot = self.layout.snapshot_flow_state();
918 self.layout.row(widths, height);
919 f(self);
920 self.layout.restore_flow_state(snapshot);
921 }
922
923 pub fn stack<F: FnOnce(&mut Self)>(&mut self, height: SizePolicy, f: F) {
928 self.stack_with_width_direction(SizePolicy::Remainder(0), height, StackDirection::TopToBottom, f);
929 }
930
931 pub fn stack_direction<F: FnOnce(&mut Self)>(&mut self, height: SizePolicy, direction: StackDirection, f: F) {
933 self.stack_with_width_direction(SizePolicy::Remainder(0), height, direction, f);
934 }
935
936 pub fn stack_with_width<F: FnOnce(&mut Self)>(&mut self, width: SizePolicy, height: SizePolicy, f: F) {
938 self.stack_with_width_direction(width, height, StackDirection::TopToBottom, f);
939 }
940
941 pub fn stack_with_width_direction<F: FnOnce(&mut Self)>(&mut self, width: SizePolicy, height: SizePolicy, direction: StackDirection, f: F) {
943 let snapshot = self.layout.snapshot_flow_state();
944 if direction == StackDirection::TopToBottom {
945 self.layout.stack(width, height);
946 } else {
947 self.layout.stack_with_direction(width, height, direction);
948 }
949 f(self);
950 self.layout.restore_flow_state(snapshot);
951 }
952
953 pub fn column<F: FnOnce(&mut Self)>(&mut self, f: F) {
955 self.layout.begin_column();
956 f(self);
957 self.layout.end_column();
958 }
959
960 pub fn next_cell(&mut self) -> Recti {
965 self.layout.next()
966 }
967
968 pub fn set_style(&mut self, style: Style) {
970 self.style = Rc::new(style);
971 }
972
973 pub fn get_style(&self) -> Style {
975 (*self.style).clone()
976 }
977
978 pub fn label(&mut self, text: &str) {
982 let (font, padding) = {
983 let style = self.style.as_ref();
984 (style.font, style.padding.max(0))
985 };
986 let text_width = if text.is_empty() {
987 0
988 } else {
989 self.atlas.get_text_size(font, text).width.max(0)
990 };
991 let line_height = self.atlas.get_font_height(font) as i32;
992 let vertical_pad = Self::vertical_text_padding(padding);
993 let preferred = Dimensioni::new(
994 text_width.saturating_add(padding.saturating_mul(2)),
995 line_height.saturating_add(vertical_pad.saturating_mul(2)),
996 );
997 let layout = self.layout.next_with_preferred(preferred);
998 self.draw_control_text(text, layout, ControlColor::Text, WidgetOption::NONE);
999 }
1000
1001 widget_layout!(
1002 #[inline(never)]
1003 button,
1005 Button
1006 );
1007
1008 widget_layout!(
1009 list_item,
1011 ListItem
1012 );
1013
1014 widget_layout!(
1015 #[inline(never)]
1016 list_box,
1018 ListBox
1019 );
1020
1021 #[inline(never)]
1022 pub fn combo_box<S: AsRef<str>>(&mut self, state: &mut Combo, items: &[S]) -> (Recti, bool, ResourceState) {
1025 state.update_items(items);
1026 let header = self.next_widget_rect(state);
1027 let opt = *state.widget_opt();
1028 let bopt = *state.behaviour_opt();
1029 let res = self.handle_widget_in_rect(state, header, None, opt, bopt);
1030 let header_clicked = res.is_submitted();
1031 let anchor = rect(header.x, header.y + header.height, header.width, 1);
1032 (anchor, header_clicked, res)
1033 }
1034
1035 widget_layout!(
1036 #[inline(never)]
1037 checkbox,
1039 Checkbox
1040 );
1041
1042 #[inline(never)]
1043 fn input_to_mouse_event(&self, control: &ControlState, input: &InputSnapshot, rect: Recti) -> MouseEvent {
1044 let orig = Vec2i::new(rect.x, rect.y);
1045
1046 let prev_pos = input.mouse_pos - input.mouse_delta - orig;
1047 let curr_pos = input.mouse_pos - orig;
1048 let mouse_down = input.mouse_down;
1049 let mouse_pressed = input.mouse_pressed;
1050
1051 if control.focused && mouse_down.is_left() {
1052 return MouseEvent::Drag { prev_pos, curr_pos };
1053 }
1054
1055 if control.hovered && mouse_pressed.is_left() {
1056 return MouseEvent::Click(curr_pos);
1057 }
1058
1059 if control.hovered {
1060 return MouseEvent::Move(curr_pos);
1061 }
1062 MouseEvent::None
1063 }
1064
1065 #[inline(never)]
1066 pub fn custom_render_widget<F: FnMut(Dimensioni, &CustomRenderArgs) + 'static>(&mut self, state: &mut Custom, f: F) {
1068 let rect = self.next_widget_rect(state);
1069 let opt = *state.widget_opt();
1070 let bopt = *state.behaviour_opt();
1071 let (control, _) = self.run_widget(state, rect, None, opt, bopt);
1072
1073 let input = self.snapshot_input();
1074 let input_ref = input.as_ref();
1075 let mouse_event = self.input_to_mouse_event(&control, input_ref, rect);
1076
1077 let active = control.focused && self.in_hover_root;
1078 let key_mods = if active { input_ref.key_mods } else { KeyMode::NONE };
1079 let key_codes = if active { input_ref.key_codes } else { KeyCode::NONE };
1080 let text_input = if active { input_ref.text_input.clone() } else { String::new() };
1081 let cra = CustomRenderArgs {
1082 content_area: rect,
1083 view: self.get_clip_rect(),
1084 mouse_event,
1085 scroll_delta: control.scroll_delta,
1086 widget_opt: state.opt,
1087 behaviour_opt: state.bopt,
1088 key_mods,
1089 key_codes,
1090 text_input,
1091 };
1092 self.command_list.push(Command::CustomRender(cra, Box::new(f)));
1093 }
1094
1095 widget_layout!(
1096 textbox,
1098 Textbox,
1099 |this: &mut Self, state: &mut Textbox, rect: Recti| {
1100 let input = Some(this.snapshot_input());
1101 let opt = state.opt | WidgetOption::HOLD_FOCUS;
1102 this.handle_widget_in_rect(state, rect, input, opt, state.bopt)
1103 }
1104 );
1105
1106 widget_layout!(
1107 textarea,
1109 TextArea,
1110 |this: &mut Self, state: &mut TextArea, rect: Recti| {
1111 let input = Some(this.snapshot_input());
1112 let opt = state.opt | WidgetOption::HOLD_FOCUS;
1113 this.handle_widget_in_rect(state, rect, input, opt, state.bopt)
1114 }
1115 );
1116
1117 widget_layout!(
1118 #[inline(never)]
1119 slider,
1121 Slider,
1122 |this: &mut Self, state: &mut Slider, rect: Recti| {
1123 let mut opt = state.opt;
1124 if state.edit.editing {
1125 opt |= WidgetOption::HOLD_FOCUS;
1126 }
1127 let input = Some(this.snapshot_input());
1128 this.handle_widget_in_rect(state, rect, input, opt, state.bopt)
1129 }
1130 );
1131
1132 widget_layout!(
1133 #[inline(never)]
1134 number,
1136 Number,
1137 |this: &mut Self, state: &mut Number, rect: Recti| {
1138 let mut opt = state.opt;
1139 if state.edit.editing {
1140 opt |= WidgetOption::HOLD_FOCUS;
1141 }
1142 let input = Some(this.snapshot_input());
1143 this.handle_widget_in_rect(state, rect, input, opt, state.bopt)
1144 }
1145 );
1146}
1147
1148#[cfg(test)]
1149mod tests {
1150 use super::*;
1151 use crate::{AtlasSource, FontEntry, SourceFormat};
1152
1153 const ICON_NAMES: [&str; 6] = ["white", "close", "expand", "collapse", "check", "expand_down"];
1154
1155 fn make_test_atlas() -> AtlasHandle {
1156 let pixels: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFF];
1157 let icons: Vec<(&str, Recti)> = ICON_NAMES.iter().map(|name| (*name, Recti::new(0, 0, 1, 1))).collect();
1158 let entries = vec![
1159 (
1160 '_',
1161 CharEntry {
1162 offset: Vec2i::new(0, 0),
1163 advance: Vec2i::new(8, 0),
1164 rect: Recti::new(0, 0, 1, 1),
1165 },
1166 ),
1167 (
1168 'a',
1169 CharEntry {
1170 offset: Vec2i::new(0, 0),
1171 advance: Vec2i::new(8, 0),
1172 rect: Recti::new(0, 0, 1, 1),
1173 },
1174 ),
1175 (
1176 'b',
1177 CharEntry {
1178 offset: Vec2i::new(0, 0),
1179 advance: Vec2i::new(8, 0),
1180 rect: Recti::new(0, 0, 1, 1),
1181 },
1182 ),
1183 ];
1184 let fonts = vec![(
1185 "default",
1186 FontEntry {
1187 line_size: 10,
1188 baseline: 8,
1189 font_size: 10,
1190 entries: &entries,
1191 },
1192 )];
1193 let source = AtlasSource {
1194 width: 1,
1195 height: 1,
1196 pixels: &pixels,
1197 icons: &icons,
1198 fonts: &fonts,
1199 format: SourceFormat::Raw,
1200 slots: &[],
1201 };
1202 AtlasHandle::from(&source)
1203 }
1204
1205 fn make_container() -> Container {
1206 let atlas = make_test_atlas();
1207 let input = Rc::new(RefCell::new(Input::default()));
1208 let mut container = Container::new("test", atlas, Rc::new(Style::default()), input);
1209 container.in_hover_root = true;
1210 container.push_container_body(rect(0, 0, 100, 30), ContainerOption::NONE, WidgetBehaviourOption::NONE);
1211 container
1212 }
1213
1214 #[test]
1215 fn scrollbars_use_current_body() {
1216 let mut container = make_container();
1217 let mut style = Style::default();
1218 style.padding = 0;
1219 style.scrollbar_size = 10;
1220 container.style = Rc::new(style);
1221
1222 container.body = rect(0, 0, 1, 1);
1223 container.content_size = Vec2i::new(0, 0);
1224
1225 let mut body = rect(0, 0, 100, 100);
1226 container.scrollbars(&mut body);
1227
1228 assert_eq!(body.width, 100);
1229 assert_eq!(body.height, 100);
1230 }
1231
1232 #[test]
1233 fn scrollbars_shrink_body_when_needed() {
1234 let mut container = make_container();
1235 let mut style = Style::default();
1236 style.padding = 0;
1237 style.scrollbar_size = 10;
1238 container.style = Rc::new(style);
1239
1240 container.content_size = Vec2i::new(200, 200);
1241
1242 let mut body = rect(0, 0, 100, 100);
1243 container.scrollbars(&mut body);
1244
1245 assert_eq!(body.width, 90);
1246 assert_eq!(body.height, 90);
1247 }
1248
1249 #[test]
1250 fn textbox_left_moves_over_multibyte() {
1251 let mut container = make_container();
1252 let input = container.input.clone();
1253 let mut state = Textbox::new("a\u{1F600}b");
1254 let textbox_id = widget_id_of(&state);
1255 container.set_focus(Some(textbox_id));
1256 state.cursor = 5;
1257
1258 input.borrow_mut().keydown_code(KeyCode::LEFT);
1259 let rect = container.layout.next();
1260 let control_state = (state.opt | WidgetOption::HOLD_FOCUS, state.bopt);
1261 let control = container.update_control(textbox_id, rect, &control_state);
1262 let input = container.snapshot_input();
1263 let mut ctx = container.widget_ctx(textbox_id, rect, Some(input));
1264 state.handle(&mut ctx, &control);
1265
1266 let cursor = state.cursor;
1267 assert_eq!(cursor, 1);
1268 }
1269
1270 #[test]
1271 fn textbox_backspace_removes_multibyte() {
1272 let mut container = make_container();
1273 let input = container.input.clone();
1274 let mut state = Textbox::new("a\u{1F600}b");
1275 let textbox_id = widget_id_of(&state);
1276 container.set_focus(Some(textbox_id));
1277 state.cursor = 5;
1278
1279 input.borrow_mut().keydown(KeyMode::BACKSPACE);
1280 let rect = container.layout.next();
1281 let control_state = (state.opt | WidgetOption::HOLD_FOCUS, state.bopt);
1282 let control = container.update_control(textbox_id, rect, &control_state);
1283 let input = container.snapshot_input();
1284 let mut ctx = container.widget_ctx(textbox_id, rect, Some(input));
1285 state.handle(&mut ctx, &control);
1286
1287 let cursor = state.cursor;
1288 assert_eq!(state.buf, "ab");
1289 assert_eq!(cursor, 1);
1290 }
1291}