From 11287c882e23e1f3081d06bf5ff3e99a02ef030a Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Wed, 12 Jul 2023 16:30:12 -0700 Subject: [PATCH 001/321] Expose methods to change viewport alignment --- widget/src/scrollable.rs | 59 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 88746ac4..3f49584c 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -554,7 +554,14 @@ pub fn update( state.scroll(delta, direction, bounds, content_bounds); - notify_on_scroll(state, on_scroll, bounds, content_bounds, shell); + notify_on_scroll( + state, + on_scroll, + bounds, + content_bounds, + direction, + shell, + ); return event::Status::Captured; } @@ -592,6 +599,7 @@ pub fn update( on_scroll, bounds, content_bounds, + direction, shell, ); } @@ -637,6 +645,7 @@ pub fn update( on_scroll, bounds, content_bounds, + direction, shell, ); @@ -672,6 +681,7 @@ pub fn update( on_scroll, bounds, content_bounds, + direction, shell, ); } @@ -712,6 +722,7 @@ pub fn update( on_scroll, bounds, content_bounds, + direction, shell, ); } @@ -747,6 +758,7 @@ pub fn update( on_scroll, bounds, content_bounds, + direction, shell, ); @@ -962,6 +974,7 @@ fn notify_on_scroll( on_scroll: &Option Message + '_>>, bounds: Rectangle, content_bounds: Rectangle, + direction: Direction, shell: &mut Shell<'_, Message>, ) { if let Some(on_scroll) = on_scroll { @@ -971,11 +984,23 @@ fn notify_on_scroll( return; } + let horizontal_alignment = direction + .horizontal() + .map(|p| p.alignment) + .unwrap_or_default(); + + let vertical_alignment = direction + .vertical() + .map(|p| p.alignment) + .unwrap_or_default(); + let viewport = Viewport { offset_x: state.offset_x, offset_y: state.offset_y, bounds, content_bounds, + vertical_alignment, + horizontal_alignment, }; // Don't publish redundant viewports to shell @@ -1080,6 +1105,8 @@ pub struct Viewport { offset_y: Offset, bounds: Rectangle, content_bounds: Rectangle, + vertical_alignment: Alignment, + horizontal_alignment: Alignment, } impl Viewport { @@ -1104,6 +1131,36 @@ impl Viewport { RelativeOffset { x, y } } + + /// Returns a new [`Viewport`] with the supplied vertical [`Alignment`]. + pub fn with_vertical_alignment(self, alignment: Alignment) -> Self { + if self.vertical_alignment != alignment { + let relative = 1.0 - self.relative_offset().y; + + Self { + offset_y: Offset::Relative(relative), + vertical_alignment: alignment, + ..self + } + } else { + self + } + } + + /// Returns a new [`Viewport`] with the supplied horizontal [`Alignment`]. + pub fn with_horizontal_alignment(self, alignment: Alignment) -> Self { + if self.horizontal_alignment != alignment { + let relative = 1.0 - self.relative_offset().x; + + Self { + offset_x: Offset::Relative(relative), + horizontal_alignment: alignment, + ..self + } + } else { + self + } + } } impl State { From a9987cb32e4d65b83f134ba54f51dffe16e93a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 13 Jul 2023 02:53:45 +0200 Subject: [PATCH 002/321] Introduce `absolute_offset_reversed` to `scrollable::Viewport` --- widget/src/scrollable.rs | 75 +++++++++------------------------------- 1 file changed, 17 insertions(+), 58 deletions(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 3f49584c..5cd94538 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -554,14 +554,7 @@ pub fn update( state.scroll(delta, direction, bounds, content_bounds); - notify_on_scroll( - state, - on_scroll, - bounds, - content_bounds, - direction, - shell, - ); + notify_on_scroll(state, on_scroll, bounds, content_bounds, shell); return event::Status::Captured; } @@ -599,7 +592,6 @@ pub fn update( on_scroll, bounds, content_bounds, - direction, shell, ); } @@ -645,7 +637,6 @@ pub fn update( on_scroll, bounds, content_bounds, - direction, shell, ); @@ -681,7 +672,6 @@ pub fn update( on_scroll, bounds, content_bounds, - direction, shell, ); } @@ -722,7 +712,6 @@ pub fn update( on_scroll, bounds, content_bounds, - direction, shell, ); } @@ -758,7 +747,6 @@ pub fn update( on_scroll, bounds, content_bounds, - direction, shell, ); @@ -974,7 +962,6 @@ fn notify_on_scroll( on_scroll: &Option Message + '_>>, bounds: Rectangle, content_bounds: Rectangle, - direction: Direction, shell: &mut Shell<'_, Message>, ) { if let Some(on_scroll) = on_scroll { @@ -984,23 +971,11 @@ fn notify_on_scroll( return; } - let horizontal_alignment = direction - .horizontal() - .map(|p| p.alignment) - .unwrap_or_default(); - - let vertical_alignment = direction - .vertical() - .map(|p| p.alignment) - .unwrap_or_default(); - let viewport = Viewport { offset_x: state.offset_x, offset_y: state.offset_y, bounds, content_bounds, - vertical_alignment, - horizontal_alignment, }; // Don't publish redundant viewports to shell @@ -1105,8 +1080,6 @@ pub struct Viewport { offset_y: Offset, bounds: Rectangle, content_bounds: Rectangle, - vertical_alignment: Alignment, - horizontal_alignment: Alignment, } impl Viewport { @@ -1122,6 +1095,22 @@ impl Viewport { AbsoluteOffset { x, y } } + /// Returns the [`AbsoluteOffset`] of the current [`Viewport`], but with its + /// alignment reversed. + /// + /// This method can be useful to switch the alignment of a [`Scrollable`] + /// while maintaining its scrolling position. + pub fn absolute_offset_reversed(&self) -> AbsoluteOffset { + let AbsoluteOffset { x, y } = self.absolute_offset(); + + AbsoluteOffset { + x: ((self.content_bounds.width - self.bounds.width).max(0.0) - x) + .max(0.0), + y: ((self.content_bounds.height - self.bounds.height).max(0.0) - y) + .max(0.0), + } + } + /// Returns the [`RelativeOffset`] of the current [`Viewport`]. pub fn relative_offset(&self) -> RelativeOffset { let AbsoluteOffset { x, y } = self.absolute_offset(); @@ -1131,36 +1120,6 @@ impl Viewport { RelativeOffset { x, y } } - - /// Returns a new [`Viewport`] with the supplied vertical [`Alignment`]. - pub fn with_vertical_alignment(self, alignment: Alignment) -> Self { - if self.vertical_alignment != alignment { - let relative = 1.0 - self.relative_offset().y; - - Self { - offset_y: Offset::Relative(relative), - vertical_alignment: alignment, - ..self - } - } else { - self - } - } - - /// Returns a new [`Viewport`] with the supplied horizontal [`Alignment`]. - pub fn with_horizontal_alignment(self, alignment: Alignment) -> Self { - if self.horizontal_alignment != alignment { - let relative = 1.0 - self.relative_offset().x; - - Self { - offset_x: Offset::Relative(relative), - horizontal_alignment: alignment, - ..self - } - } else { - self - } - } } impl State { From d36758405789d6a305d978eefb46a1ca90d141d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 13 Jul 2023 03:01:53 +0200 Subject: [PATCH 003/321] Avoid redundant `max` in `absolute_offset_reversed` I believe `Offset::absolute` guarantees the offset never exceeds the maximum scrolling boundaries already. --- widget/src/scrollable.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 5cd94538..e0aeeebd 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1104,10 +1104,8 @@ impl Viewport { let AbsoluteOffset { x, y } = self.absolute_offset(); AbsoluteOffset { - x: ((self.content_bounds.width - self.bounds.width).max(0.0) - x) - .max(0.0), - y: ((self.content_bounds.height - self.bounds.height).max(0.0) - y) - .max(0.0), + x: (self.content_bounds.width - self.bounds.width).max(0.0) - x, + y: (self.content_bounds.height - self.bounds.height).max(0.0) - y, } } From dc0ebdc525dcb234fb754248eb1ee1606f91e839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 13 Jul 2023 16:40:47 +0200 Subject: [PATCH 004/321] Fix new `clippy` lint in `pokedex` example --- examples/pokedex/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index 1873b674..4482814c 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -151,7 +151,7 @@ impl Pokemon { } let id = { - let mut rng = rand::rngs::OsRng::default(); + let mut rng = rand::rngs::OsRng; rng.gen_range(0, Pokemon::TOTAL) }; From 66d671066386cd4ec1addbdfe0750e3077a5ea51 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 13 Jul 2023 12:10:10 -0700 Subject: [PATCH 005/321] Dont blink input cursor when window loses focus --- widget/src/text_input.rs | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 272263f9..bd3145ea 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -564,6 +564,7 @@ where Some(Focus { updated_at: now, now, + is_window_focused: true, }) }) } else { @@ -919,19 +920,35 @@ where state.keyboard_modifiers = modifiers; } + Event::Window(window::Event::Unfocused) => { + let state = state(); + + if let Some(focus) = &mut state.is_focused { + focus.is_window_focused = false; + } + } + Event::Window(window::Event::Focused) => { + let state = state(); + + if let Some(focus) = &mut state.is_focused { + focus.is_window_focused = true; + } + } Event::Window(window::Event::RedrawRequested(now)) => { let state = state(); if let Some(focus) = &mut state.is_focused { - focus.now = now; + if focus.is_window_focused { + focus.now = now; - let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS - - (now - focus.updated_at).as_millis() - % CURSOR_BLINK_INTERVAL_MILLIS; + let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS + - (now - focus.updated_at).as_millis() + % CURSOR_BLINK_INTERVAL_MILLIS; - shell.request_redraw(window::RedrawRequest::At( - now + Duration::from_millis(millis_until_redraw as u64), - )); + shell.request_redraw(window::RedrawRequest::At( + now + Duration::from_millis(millis_until_redraw as u64), + )); + } } } _ => {} @@ -1016,7 +1033,11 @@ pub fn draw( let font = font.unwrap_or_else(|| renderer.default_font()); let size = size.unwrap_or_else(|| renderer.default_size()); - let (cursor, offset) = if let Some(focus) = &state.is_focused { + let (cursor, offset) = if let Some(focus) = state + .is_focused + .as_ref() + .filter(|focus| focus.is_window_focused) + { match state.cursor.state(value) { cursor::State::Index(position) => { let (text_value_width, offset) = @@ -1188,6 +1209,7 @@ pub struct State { struct Focus { updated_at: Instant, now: Instant, + is_window_focused: bool, } impl State { @@ -1225,6 +1247,7 @@ impl State { self.is_focused = Some(Focus { updated_at: now, now, + is_window_focused: true, }); self.move_cursor_to_end(); From 44c07323067fa8c09122356c111047082d946c59 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 13 Jul 2023 12:21:24 -0700 Subject: [PATCH 006/321] Restart animation when regaining focus --- widget/src/text_input.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index bd3145ea..a335afbc 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -932,6 +932,9 @@ where if let Some(focus) = &mut state.is_focused { focus.is_window_focused = true; + focus.updated_at = Instant::now(); + + shell.request_redraw(window::RedrawRequest::NextFrame); } } Event::Window(window::Event::RedrawRequested(now)) => { From 42c423b4a89613c4e1c552c891c1391a34837122 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 15 Jul 2023 10:04:25 -0700 Subject: [PATCH 007/321] Add viewport to Widget::on_event --- core/src/element.rs | 9 ++++++--- core/src/widget.rs | 1 + examples/loading_spinners/src/circular.rs | 1 + examples/loading_spinners/src/linear.rs | 1 + examples/modal/src/main.rs | 3 +++ examples/toast/src/main.rs | 5 +++++ runtime/src/user_interface.rs | 3 +++ widget/src/button.rs | 2 ++ widget/src/canvas.rs | 1 + widget/src/checkbox.rs | 1 + widget/src/column.rs | 2 ++ widget/src/container.rs | 2 ++ widget/src/image/viewer.rs | 1 + widget/src/lazy.rs | 2 ++ widget/src/lazy/component.rs | 2 ++ widget/src/lazy/responsive.rs | 2 ++ widget/src/mouse_area.rs | 2 ++ widget/src/overlay/menu.rs | 4 ++++ widget/src/pane_grid.rs | 2 ++ widget/src/pane_grid/content.rs | 3 +++ widget/src/pane_grid/title_bar.rs | 3 +++ widget/src/pick_list.rs | 1 + widget/src/radio.rs | 1 + widget/src/row.rs | 2 ++ widget/src/scrollable.rs | 20 ++++++++++++++++++-- widget/src/slider.rs | 1 + widget/src/text_input.rs | 1 + widget/src/toggler.rs | 1 + widget/src/tooltip.rs | 2 ++ widget/src/vertical_slider.rs | 1 + 30 files changed, 77 insertions(+), 5 deletions(-) diff --git a/core/src/element.rs b/core/src/element.rs index 3268f14b..b9b76247 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -380,6 +380,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, B>, + viewport: &Rectangle, ) -> event::Status { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); @@ -392,6 +393,7 @@ where renderer, clipboard, &mut local_shell, + viewport, ); shell.merge(local_shell, &self.mapper); @@ -511,10 +513,11 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { - self.element - .widget - .on_event(state, event, layout, cursor, renderer, clipboard, shell) + self.element.widget.on_event( + state, event, layout, cursor, renderer, clipboard, shell, viewport, + ) } fn draw( diff --git a/core/src/widget.rs b/core/src/widget.rs index 79d86444..25c1cae8 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -115,6 +115,7 @@ where _renderer: &Renderer, _clipboard: &mut dyn Clipboard, _shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { event::Status::Ignored } diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index 3a35e029..3898d76e 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -272,6 +272,7 @@ where _renderer: &iced::Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { const FRAME_RATE: u64 = 60; diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs index 3d95729b..20fbe9f3 100644 --- a/examples/loading_spinners/src/linear.rs +++ b/examples/loading_spinners/src/linear.rs @@ -193,6 +193,7 @@ where _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { const FRAME_RATE: u64 = 60; diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index 7fcbbfe4..8a48f830 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -300,6 +300,7 @@ mod modal { renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { self.base.as_widget_mut().on_event( &mut state.children[0], @@ -309,6 +310,7 @@ mod modal { renderer, clipboard, shell, + viewport, ) } @@ -446,6 +448,7 @@ mod modal { renderer, clipboard, shell, + &layout.bounds(), ) } diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 4282ddcf..5d29e895 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -400,6 +400,7 @@ mod toast { renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { self.content.as_widget_mut().on_event( &mut state.children[0], @@ -409,6 +410,7 @@ mod toast { renderer, clipboard, shell, + viewport, ) } @@ -559,6 +561,8 @@ mod toast { } } + let viewport = layout.bounds(); + self.toasts .iter_mut() .zip(self.state.iter_mut()) @@ -576,6 +580,7 @@ mod toast { renderer, clipboard, &mut local_shell, + &viewport, ); if !local_shell.is_empty() { diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 619423fd..e31ea98f 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -284,6 +284,8 @@ where (cursor, vec![event::Status::Ignored; events.len()]) }; + let viewport = Rectangle::with_size(self.bounds); + let _ = ManuallyDrop::into_inner(manual_overlay); let event_statuses = events @@ -305,6 +307,7 @@ where renderer, clipboard, &mut shell, + &viewport, ); if matches!(event_status, event::Status::Captured) { diff --git a/widget/src/button.rs b/widget/src/button.rs index 8ebc9657..1312095f 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -200,6 +200,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { if let event::Status::Captured = self.content.as_widget_mut().on_event( &mut tree.children[0], @@ -209,6 +210,7 @@ where renderer, clipboard, shell, + viewport, ) { return event::Status::Captured; } diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 96062038..1a186432 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -147,6 +147,7 @@ where _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { let bounds = layout.bounds(); diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index aa0bff42..310a67ed 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -208,6 +208,7 @@ where _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) diff --git a/widget/src/column.rs b/widget/src/column.rs index d92d794b..9271d5ef 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -170,6 +170,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { self.children .iter_mut() @@ -184,6 +185,7 @@ where renderer, clipboard, shell, + viewport, ) }) .fold(event::Status::Ignored, event::Status::merge) diff --git a/widget/src/container.rs b/widget/src/container.rs index da9a31d6..64cf5cd5 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -200,6 +200,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { self.content.as_widget_mut().on_event( &mut tree.children[0], @@ -209,6 +210,7 @@ where renderer, clipboard, shell, + viewport, ) } diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 8040d6bd..0038f858 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -148,6 +148,7 @@ where renderer: &Renderer, _clipboard: &mut dyn Clipboard, _shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { let bounds = layout.bounds(); diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index da287f06..761f45ad 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -186,6 +186,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { self.with_element_mut(|element| { element.as_widget_mut().on_event( @@ -196,6 +197,7 @@ where renderer, clipboard, shell, + viewport, ) }) } diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index c7814966..bc0e23df 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -270,6 +270,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); @@ -284,6 +285,7 @@ where renderer, clipboard, &mut local_shell, + viewport, ) }); diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index 07300857..b56545c8 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -182,6 +182,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { let state = tree.state.downcast_mut::(); let mut content = self.content.borrow_mut(); @@ -203,6 +204,7 @@ where renderer, clipboard, &mut local_shell, + viewport, ) }, ); diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index da7dc88f..490f7c48 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -150,6 +150,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { if let event::Status::Captured = self.content.as_widget_mut().on_event( &mut tree.children[0], @@ -159,6 +160,7 @@ where renderer, clipboard, shell, + viewport, ) { return event::Status::Captured; } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index ccf4dfb5..72662422 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -268,8 +268,11 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { + let bounds = layout.bounds(); + self.container.on_event( self.state, event, layout, cursor, renderer, clipboard, shell, + &bounds, ) } @@ -377,6 +380,7 @@ where renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 31bb0e86..4f6dfbe8 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -317,6 +317,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { let action = tree.state.downcast_mut::(); @@ -357,6 +358,7 @@ where renderer, clipboard, shell, + viewport, is_picked, ) }) diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index c28ae6e3..e890e41a 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -222,6 +222,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, is_picked: bool, ) -> event::Status { let mut event_status = event::Status::Ignored; @@ -237,6 +238,7 @@ where renderer, clipboard, shell, + viewport, ); children.next().unwrap() @@ -255,6 +257,7 @@ where renderer, clipboard, shell, + viewport, ) }; diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 2fe79f80..cac24e68 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -304,6 +304,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { let mut children = layout.children(); let padded = children.next().unwrap(); @@ -328,6 +329,7 @@ where renderer, clipboard, shell, + viewport, ) } else { event::Status::Ignored @@ -342,6 +344,7 @@ where renderer, clipboard, shell, + viewport, ) } else { event::Status::Ignored diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 832aae6b..d99ada10 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -200,6 +200,7 @@ where _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { update( event, diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 5b883147..65d71ec2 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -233,6 +233,7 @@ where _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) diff --git a/widget/src/row.rs b/widget/src/row.rs index 1db22416..7baaaae3 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -159,6 +159,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { self.children .iter_mut() @@ -173,6 +174,7 @@ where renderer, clipboard, shell, + viewport, ) }) .fold(event::Status::Ignored, event::Status::merge) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index e0aeeebd..f621fb26 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -278,6 +278,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { update( tree.state.downcast_mut::(), @@ -288,7 +289,7 @@ where shell, self.direction, &self.on_scroll, - |event, layout, cursor, clipboard, shell| { + |event, layout, cursor, clipboard, shell, viewport| { self.content.as_widget_mut().on_event( &mut tree.children[0], event, @@ -297,6 +298,7 @@ where renderer, clipboard, shell, + viewport, ) }, ) @@ -492,6 +494,7 @@ pub fn update( mouse::Cursor, &mut dyn Clipboard, &mut Shell<'_, Message>, + &Rectangle, ) -> event::Status, ) -> event::Status { let bounds = layout.bounds(); @@ -518,7 +521,20 @@ pub fn update( _ => mouse::Cursor::Unavailable, }; - update_content(event.clone(), content, cursor, clipboard, shell) + let translation = state.translation(direction, bounds, content_bounds); + + update_content( + event.clone(), + content, + cursor, + clipboard, + shell, + &Rectangle { + y: bounds.y + translation.y, + x: bounds.x + translation.x, + ..bounds + }, + ) }; if let event::Status::Captured = event_status { diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 3ea4391b..e41be7c9 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -187,6 +187,7 @@ where _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { update( event, diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index a335afbc..9958cbcc 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -302,6 +302,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { update( event, diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 1b31765f..c8187181 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -207,6 +207,7 @@ where _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 2dc3da01..ff7f960f 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -147,6 +147,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { let state = tree.state.downcast_mut::(); @@ -163,6 +164,7 @@ where renderer, clipboard, shell, + viewport, ) } diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 91f2b466..efca302a 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -184,6 +184,7 @@ where _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { update( event, From 189817594f3aa8db0c2ac00b3d4128ae7733da45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 20 Jul 2023 20:39:49 +0200 Subject: [PATCH 008/321] Update `CHANGELOG` --- CHANGELOG.md | 89 +++++++++++++++++++++++++++++++++++++++++ docs/release_summary.py | 5 ++- 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 077f4af3..632beba2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,95 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Text shaping, font fallback, and `iced_wgpu` overhaul. [#1697](https://github.com/iced-rs/iced/pull/1697) +- Software renderer, runtime renderer fallback, and core consolidation. [#1748](https://github.com/iced-rs/iced/pull/1748) +- Incremental rendering for `iced_tiny_skia`. [#1811](https://github.com/iced-rs/iced/pull/1811) +- Configurable `LineHeight` support for text widgets. [#1828](https://github.com/iced-rs/iced/pull/1828) +- `text::Shaping` strategy selection. [#1822](https://github.com/iced-rs/iced/pull/1822) +- Subpixel glyph positioning and layout linearity. [#1921](https://github.com/iced-rs/iced/pull/1921) +- Background gradients. [#1846](https://github.com/iced-rs/iced/pull/1846) +- Offscreen rendering and screenshots. [#1845](https://github.com/iced-rs/iced/pull/1845) +- Nested overlays. [#1719](https://github.com/iced-rs/iced/pull/1719) +- Cursor availability. [#1904](https://github.com/iced-rs/iced/pull/1904) +- Backend-specific primitives. [#1932](https://github.com/iced-rs/iced/pull/1932) +- `web-colors` feature flag to enable "sRGB linear" blending. [#1888](https://github.com/iced-rs/iced/pull/1888) +- `PaneGrid` logic to split panes by drag & drop. [#1856](https://github.com/iced-rs/iced/pull/1856) +- `PaneGrid` logic to drag & drop panes to the edges. [#1865](https://github.com/iced-rs/iced/pull/1865) +- Type-safe `Scrollable` direction. [#1878](https://github.com/iced-rs/iced/pull/1878) +- `Scrollable` alignment. [#1912](https://github.com/iced-rs/iced/pull/1912) +- Helpers to change viewport alignment of a `Scrollable`. [#1953](https://github.com/iced-rs/iced/pull/1953) +- `scroll_to` widget operation. [#1796](https://github.com/iced-rs/iced/pull/1796) +- `scroll_to` helper. [#1804](https://github.com/iced-rs/iced/pull/1804) +- Command to fetch window size. [#1927](https://github.com/iced-rs/iced/pull/1927) +- Conversion support from `Fn` trait to custom theme. [#1861](https://github.com/iced-rs/iced/pull/1861) +- Configurable border radii on relevant widgets. [#1869](https://github.com/iced-rs/iced/pull/1869) +- `border_radius` styling to `Slider` rail. [#1892](https://github.com/iced-rs/iced/pull/1892) +- Aliased entries in `text::Cache`. [#1934](https://github.com/iced-rs/iced/pull/1934) +- Text cache modes. [#1938](https://github.com/iced-rs/iced/pull/1938) +- `operate` method for `program::State`. [#1913](https://github.com/iced-rs/iced/pull/1913) +- Nix instructions to `DEPENDENCIES.md`. [#1859](https://github.com/iced-rs/iced/pull/1859) +- Loading spinners example. [#1902](https://github.com/iced-rs/iced/pull/1902) +- `Viewport` argument to `Widget::on_event`. [#1956](https://github.com/iced-rs/iced/pull/1956) + +### Changed +- Updated `wgpu` to `0.16`. [#1807](https://github.com/iced-rs/iced/pull/1807) +- Updated `glam` to `0.24`. [#1840](https://github.com/iced-rs/iced/pull/1840) +- Updated `winit` to `0.28`. [#1738](https://github.com/iced-rs/iced/pull/1738) +- Updated `palette` to `0.7`. [#1875](https://github.com/iced-rs/iced/pull/1875) +- Updated `ouroboros` to `0.17`. [#1925](https://github.com/iced-rs/iced/pull/1925) +- Updated `resvg` to `0.35` and `tiny-skia` to `0.10`. [#1907](https://github.com/iced-rs/iced/pull/1907) +- Changed `mouse::Button::Other` to take `u16` instead of `u8`. [#1797](https://github.com/iced-rs/iced/pull/1797) +- Changed `subscription::channel` to take a `FnOnce` non-`Sync` closure. [#1917](https://github.com/iced-rs/iced/pull/1917) +- Removed `Copy` requirement for text `StyleSheet::Style`. [#1814](https://github.com/iced-rs/iced/pull/1814) +- Removed `min_width` of 1 from scrollbar & scroller for `Scrollable`. [#1844](https://github.com/iced-rs/iced/pull/1844) +- Used `Widget::overlay` for `Tooltip`. [#1692](https://github.com/iced-rs/iced/pull/1692) + +### Fixed +- Invalidate `Responsive` layout when shell layout is invalidated. [#1799](https://github.com/iced-rs/iced/pull/1799) +- Invalidate `Responsive` layout when size changes without a `view` call. [#1890](https://github.com/iced-rs/iced/pull/1890) +- Broken link in `ROADMAP.md`. [#1815](https://github.com/iced-rs/iced/pull/1815) +- `bounds` of selected option background in `Menu`. [#1831](https://github.com/iced-rs/iced/pull/1831) +- Border radius logic in `iced_tiny_skia`. [#1842](https://github.com/iced-rs/iced/pull/1842) +- `Svg` filtered color not premultiplied. [#1841](https://github.com/iced-rs/iced/pull/1841) +- Race condition when growing an `image::Atlas`. [#1847](https://github.com/iced-rs/iced/pull/1847) +- Clearing damaged surface with background color in `iced_tiny_skia`. [#1854](https://github.com/iced-rs/iced/pull/1854) +- Private gradient pack logic for `iced_graphics::Gradient`. [#1871](https://github.com/iced-rs/iced/pull/1871) +- Unordered quads of different background types. [#1873](https://github.com/iced-rs/iced/pull/1873) +- Panic in `glyphon` when glyphs are missing. [#1883](https://github.com/iced-rs/iced/pull/1883) +- Empty scissor rectangle in `iced_wgpu::triangle` pipeline. [#1893](https://github.com/iced-rs/iced/pull/1893) +- `Scrollable` scrolling when mouse not over it. [#1910](https://github.com/iced-rs/iced/pull/1910) +- `translation` in `layout` of `Nested` overlay. [#1924](https://github.com/iced-rs/iced/pull/1924) +- Build when using vendored dependencies. [#1928](https://github.com/iced-rs/iced/pull/1928) +- Minor grammar mistake. [#1931](https://github.com/iced-rs/iced/pull/1931) +- Quad rendering including border only inside of the bounds. [#1843](https://github.com/iced-rs/iced/pull/1843) +- Redraw requests not being forwarded for `Component` overlays. [#1949](https://github.com/iced-rs/iced/pull/1949) +- Blinking input cursor when window loses focus. [#1955](https://github.com/iced-rs/iced/pull/1955) + +Many thanks to... + +- @a1phyr +- @alec-deason +- @AustinMReppert +- @bbb651 +- @bungoboingo +- @casperstorm +- @clarkmoody +- @Davidster +- @Drakulix +- @GyulyVGC +- @ids1024 +- @jhff +- @JonathanLindsey +- @kr105 +- @marienz +- @nicksenger +- @nicoburns +- @RGBCube +- @tarkah +- @thunderstorm010 +- @wash2 + ## [0.9.0] - 2023-04-13 ### Added - `MouseArea` widget. [#1594](https://github.com/iced-rs/iced/pull/1594) diff --git a/docs/release_summary.py b/docs/release_summary.py index cd4593b5..62694d05 100644 --- a/docs/release_summary.py +++ b/docs/release_summary.py @@ -13,7 +13,7 @@ PR_COMMIT_REGEX = re.compile(r"(?i)Merge pull request #(\d+).*") def get_merged_prs_since_release(repo: str, previous_release_branch: str) -> List[Tuple[str, int, str, str]]: prs = [] - compare_url = f"https://api.github.com/repos/{repo}/compare/{previous_release_branch}...master" + compare_url = f"https://api.github.com/repos/{repo}/compare/{previous_release_branch}...master?per_page=1000" compare_response = requests.get(compare_url, headers=HEADERS) if compare_response.status_code == 200: @@ -23,7 +23,10 @@ def get_merged_prs_since_release(repo: str, previous_release_branch: str) -> Lis if match: pr_number = int(match.group(1)) pr_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}" + + print(f"Querying PR {pr_number}") pr_response = requests.get(pr_url, headers=HEADERS) + if pr_response.status_code == 200: pr_data = pr_response.json() prs.append((pr_data["title"], pr_number, pr_data["html_url"], pr_data["user"]["login"])) From 25936e441952994c7791b0a3ac907ab26c3c012e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 20 Jul 2023 20:44:14 +0200 Subject: [PATCH 009/321] Fix consistency in `CHANGELOG` --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 632beba2..7e647e25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,8 +51,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Used `Widget::overlay` for `Tooltip`. [#1692](https://github.com/iced-rs/iced/pull/1692) ### Fixed -- Invalidate `Responsive` layout when shell layout is invalidated. [#1799](https://github.com/iced-rs/iced/pull/1799) -- Invalidate `Responsive` layout when size changes without a `view` call. [#1890](https://github.com/iced-rs/iced/pull/1890) +- `Responsive` layout not invalidated when shell layout is invalidated. [#1799](https://github.com/iced-rs/iced/pull/1799) +- `Responsive` layout not invalidated when size changes without a `view` call. [#1890](https://github.com/iced-rs/iced/pull/1890) - Broken link in `ROADMAP.md`. [#1815](https://github.com/iced-rs/iced/pull/1815) - `bounds` of selected option background in `Menu`. [#1831](https://github.com/iced-rs/iced/pull/1831) - Border radius logic in `iced_tiny_skia`. [#1842](https://github.com/iced-rs/iced/pull/1842) From b6bee55fa23d5f205d4cb6ed7bfde60c60b84373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 20 Jul 2023 20:44:32 +0200 Subject: [PATCH 010/321] Rearrange item in `CHANGELOG` --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e647e25..73a844dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,9 +33,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Aliased entries in `text::Cache`. [#1934](https://github.com/iced-rs/iced/pull/1934) - Text cache modes. [#1938](https://github.com/iced-rs/iced/pull/1938) - `operate` method for `program::State`. [#1913](https://github.com/iced-rs/iced/pull/1913) +- `Viewport` argument to `Widget::on_event`. [#1956](https://github.com/iced-rs/iced/pull/1956) - Nix instructions to `DEPENDENCIES.md`. [#1859](https://github.com/iced-rs/iced/pull/1859) - Loading spinners example. [#1902](https://github.com/iced-rs/iced/pull/1902) -- `Viewport` argument to `Widget::on_event`. [#1956](https://github.com/iced-rs/iced/pull/1956) ### Changed - Updated `wgpu` to `0.16`. [#1807](https://github.com/iced-rs/iced/pull/1807) From 95ff96f71f8f8069608ad08985e2b1214dd42284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 7 Jul 2023 07:12:37 +0200 Subject: [PATCH 011/321] Update `cosmic-text` and `glyphon` --- tiny_skia/Cargo.toml | 5 +---- wgpu/Cargo.toml | 2 +- wgpu/src/backend.rs | 53 +++++++++++--------------------------------- wgpu/src/text.rs | 22 +++++------------- 4 files changed, 21 insertions(+), 61 deletions(-) diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index d9276ea5..66ad35fd 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -12,6 +12,7 @@ geometry = ["iced_graphics/geometry"] raw-window-handle = "0.5" softbuffer = "0.2" tiny-skia = "0.10" +cosmic-text = "0.9" bytemuck = "1" rustc-hash = "1.1" kurbo = "0.9" @@ -21,10 +22,6 @@ log = "0.4" version = "0.8" path = "../graphics" -[dependencies.cosmic-text] -git = "https://github.com/hecrj/cosmic-text.git" -rev = "c3cd24dc972bb8fd55d016c81ac9fa637e0a4ada" - [dependencies.twox-hash] version = "1.6" default-features = false diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 22cfad55..a47c635a 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -45,7 +45,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "8324f20158a62f8520bad4ed09f6aa5552f8f2a6" +rev = "886f47c0a9905af340b07a488c953ac00c4bf370" [dependencies.glam] version = "0.24" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 4a0c54f0..9966a38c 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -94,18 +94,11 @@ impl Backend { queue, encoder, scale_factor, + target_size, transformation, &layers, ); - while !self.prepare_text( - device, - queue, - scale_factor, - target_size, - &layers, - ) {} - self.render( device, encoder, @@ -124,44 +117,13 @@ impl Backend { self.image_pipeline.end_frame(); } - fn prepare_text( - &mut self, - device: &wgpu::Device, - queue: &wgpu::Queue, - scale_factor: f32, - target_size: Size, - layers: &[Layer<'_>], - ) -> bool { - for layer in layers { - let bounds = (layer.bounds * scale_factor).snap(); - - if bounds.width < 1 || bounds.height < 1 { - continue; - } - - if !layer.text.is_empty() - && !self.text_pipeline.prepare( - device, - queue, - &layer.text, - layer.bounds, - scale_factor, - target_size, - ) - { - return false; - } - } - - true - } - fn prepare( &mut self, device: &wgpu::Device, queue: &wgpu::Queue, _encoder: &mut wgpu::CommandEncoder, scale_factor: f32, + target_size: Size, transformation: Transformation, layers: &[Layer<'_>], ) { @@ -210,6 +172,17 @@ impl Backend { ); } } + + if !layer.text.is_empty() { + self.text_pipeline.prepare( + device, + queue, + &layer.text, + layer.bounds, + scale_factor, + target_size, + ); + } } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 65d3b818..ef910c39 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -35,7 +35,7 @@ impl Pipeline { .into_iter(), )), renderers: Vec::new(), - atlas: glyphon::TextAtlas::new( + atlas: glyphon::TextAtlas::with_color_mode( device, queue, format, @@ -66,7 +66,7 @@ impl Pipeline { bounds: Rectangle, scale_factor: f32, target_size: Size, - ) -> bool { + ) { if self.renderers.len() <= self.prepare_layer { self.renderers.push(glyphon::TextRenderer::new( &mut self.atlas, @@ -188,21 +188,11 @@ impl Pipeline { match result { Ok(()) => { self.prepare_layer += 1; - - true } - Err(glyphon::PrepareError::AtlasFull(content_type)) => { - self.prepare_layer = 0; - - #[allow(clippy::needless_bool)] - if self.atlas.grow(device, content_type) { - false - } else { - // If the atlas cannot grow, then all bets are off. - // Instead of panicking, we will just pray that the result - // will be somewhat readable... - true - } + Err(glyphon::PrepareError::AtlasFull) => { + // If the atlas cannot grow, then all bets are off. + // Instead of panicking, we will just pray that the result + // will be somewhat readable... } } } From 1006206fb260e6382ddf50a543846ccb9ffa957b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 20 Jul 2023 20:48:39 +0200 Subject: [PATCH 012/321] Use official `glyphon` repository instead of fork --- wgpu/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index a47c635a..a0500f83 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -44,8 +44,8 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" -git = "https://github.com/hecrj/glyphon.git" -rev = "886f47c0a9905af340b07a488c953ac00c4bf370" +git = "https://github.com/grovesNL/glyphon.git" +rev = "81dedbd04237e181c2118931c5f37d341aeb6837" [dependencies.glam] version = "0.24" From 2a05ef9601bca560e68f9a16ff1875cfed33e0ea Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Mon, 24 Jul 2023 09:26:24 -0700 Subject: [PATCH 013/321] Don't clip raw overlay bounds User interface wraps the overlay in `overlay::Nested`. Clipping here w/ the base Nested overlay always clipped at (0, 0) position instead of the correct position of the child overlay. It's clipped properly already within `Nested::draw`. --- runtime/src/user_interface.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index e31ea98f..8a936b98 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -513,17 +513,13 @@ where renderer, ); - let overlay_bounds = layout.bounds(); - - renderer.with_layer(overlay_bounds, |renderer| { - overlay.draw( - renderer, - theme, - style, - Layout::new(layout), - cursor, - ); - }); + overlay.draw( + renderer, + theme, + style, + Layout::new(layout), + cursor, + ); if cursor .position() From 355ef8188a34d63eb0d93bd8b3329f6a15e68e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 26 Jul 2023 02:09:16 +0200 Subject: [PATCH 014/321] Add workflow to verify `CHANGELOG` is always up-to-date --- .github/workflows/verify.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/verify.yml diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml new file mode 100644 index 00000000..f7342ac1 --- /dev/null +++ b/.github/workflows/verify.yml @@ -0,0 +1,17 @@ +name: Verify +on: + pull_request: + branches: + - master +jobs: + changelog: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Check `CHANGELOG.md` has changes + run: | + ! git diff --exit-code master HEAD CHANGELOG.md + - name: Check `CHANGELOG.md` has PR author + if: ${{ github.event.pull_request.user.name != 'hecrj' }} + run: | + sed -n '/## \[Unreleased\]/,/^## /p' CHANGELOG.md | sed -n '/Many thanks to.../,//p' | grep '@${{ github.event.pull_request.user.name }}' From 269e5410da8a9d04b62671042167ea475283d678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 26 Jul 2023 02:19:25 +0200 Subject: [PATCH 015/321] Fetch all repository history in `verify` workflow --- .github/workflows/verify.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index f7342ac1..6f48415a 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -8,6 +8,8 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 + with: + fetch-depth: 0 - name: Check `CHANGELOG.md` has changes run: | ! git diff --exit-code master HEAD CHANGELOG.md From d9faf4c9808f9959ac3dcba052c4c2febd1d0481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 26 Jul 2023 02:19:44 +0200 Subject: [PATCH 016/321] Use `login` instead of `name` in `verify` workflow --- .github/workflows/verify.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 6f48415a..84778e91 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -14,6 +14,6 @@ jobs: run: | ! git diff --exit-code master HEAD CHANGELOG.md - name: Check `CHANGELOG.md` has PR author - if: ${{ github.event.pull_request.user.name != 'hecrj' }} + if: ${{ github.event.pull_request.user.login != 'hecrj' }} run: | - sed -n '/## \[Unreleased\]/,/^## /p' CHANGELOG.md | sed -n '/Many thanks to.../,//p' | grep '@${{ github.event.pull_request.user.name }}' + sed -n '/## \[Unreleased\]/,/^## /p' CHANGELOG.md | sed -n '/Many thanks to.../,//p' | grep '@${{ github.event.pull_request.user.login }}' From 0be3fe4ec760dd4eba1b753b87b6f89c61d29747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 26 Jul 2023 02:24:22 +0200 Subject: [PATCH 017/321] Use `origin/master` instead of `master` in `verify` workflow --- .github/workflows/verify.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 84778e91..3de8a21c 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -12,7 +12,7 @@ jobs: fetch-depth: 0 - name: Check `CHANGELOG.md` has changes run: | - ! git diff --exit-code master HEAD CHANGELOG.md + ! git diff --exit-code origin/master HEAD -- CHANGELOG.md - name: Check `CHANGELOG.md` has PR author if: ${{ github.event.pull_request.user.login != 'hecrj' }} run: | From 6531f2789702d3490b3e7b74686c03954fd14a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 26 Jul 2023 02:30:37 +0200 Subject: [PATCH 018/321] Update `CHANGELOG` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a844dd..b7174bb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Viewport` argument to `Widget::on_event`. [#1956](https://github.com/iced-rs/iced/pull/1956) - Nix instructions to `DEPENDENCIES.md`. [#1859](https://github.com/iced-rs/iced/pull/1859) - Loading spinners example. [#1902](https://github.com/iced-rs/iced/pull/1902) +- Workflow that verifies `CHANGELOG` is always up-to-date. [#1970](https://github.com/iced-rs/iced/pull/1970) ### Changed - Updated `wgpu` to `0.16`. [#1807](https://github.com/iced-rs/iced/pull/1807) From 94e991a785eddcc1c706a5a7111e1e88b18aef41 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Fri, 21 Jul 2023 13:57:49 -0700 Subject: [PATCH 019/321] Add app id setting for linux --- winit/src/settings.rs | 28 +++++++++++++++++++++++++++- winit/src/settings/linux.rs | 11 +++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 winit/src/settings/linux.rs diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 40b3d487..8d3e1b47 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -7,6 +7,10 @@ mod platform; #[path = "settings/macos.rs"] mod platform; +#[cfg(target_os = "linux")] +#[path = "settings/linux.rs"] +mod platform; + #[cfg(target_arch = "wasm32")] #[path = "settings/wasm.rs"] mod platform; @@ -14,6 +18,7 @@ mod platform; #[cfg(not(any( target_os = "windows", target_os = "macos", + target_os = "linux", target_arch = "wasm32" )))] #[path = "settings/other.rs"] @@ -150,7 +155,6 @@ impl Window { } #[cfg(any( - target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", @@ -192,6 +196,28 @@ impl Window { ); } + #[cfg(target_os = "linux")] + { + #[cfg(feature = "x11")] + { + use winit::platform::x11::WindowBuilderExtX11; + + window_builder = window_builder.with_name( + &self.platform_specific.application_id, + &self.platform_specific.application_id, + ); + } + #[cfg(feature = "wayland")] + { + use winit::platform::wayland::WindowBuilderExtWayland; + + window_builder = window_builder.with_name( + &self.platform_specific.application_id, + &self.platform_specific.application_id, + ); + } + } + window_builder } } diff --git a/winit/src/settings/linux.rs b/winit/src/settings/linux.rs new file mode 100644 index 00000000..009b9d9e --- /dev/null +++ b/winit/src/settings/linux.rs @@ -0,0 +1,11 @@ +//! Platform specific settings for Linux. + +/// The platform specific window settings of an application. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct PlatformSpecific { + /// Sets the application id of the window. + /// + /// As a best practice, it is suggested to select an application id that match + /// the basename of the application’s .desktop file. + pub application_id: String, +} From 13c8d965d3295c83a6b77b1831db1139833a5b70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 26 Jul 2023 20:15:49 +0200 Subject: [PATCH 020/321] Update `CHANGELOG` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7174bb8..4481008f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Conversion support from `Fn` trait to custom theme. [#1861](https://github.com/iced-rs/iced/pull/1861) - Configurable border radii on relevant widgets. [#1869](https://github.com/iced-rs/iced/pull/1869) - `border_radius` styling to `Slider` rail. [#1892](https://github.com/iced-rs/iced/pull/1892) +- `application_id` in `PlatformSpecific` settings for Linux. [#1963](https://github.com/iced-rs/iced/pull/1963) - Aliased entries in `text::Cache`. [#1934](https://github.com/iced-rs/iced/pull/1934) - Text cache modes. [#1938](https://github.com/iced-rs/iced/pull/1938) - `operate` method for `program::State`. [#1913](https://github.com/iced-rs/iced/pull/1913) From 9afcf067dbed26b6640dde8de9c24f90eea5c3c1 Mon Sep 17 00:00:00 2001 From: Redhawk18 Date: Tue, 18 Jul 2023 14:09:13 -0400 Subject: [PATCH 021/321] Link to graphical roadmap in `ROADMAP.md` --- ROADMAP.md | 116 +---------------------------------------------------- 1 file changed, 1 insertion(+), 115 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index f1893664..afcece7c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,120 +1,6 @@ # Roadmap -This document describes the current state of Iced and some of the most important next steps we should take before it can become a production-ready GUI library. This list keeps the short term new features in sight in order to coordinate work and discussion. Therefore, it is not meant to be exhaustive. +We have [a detailed graphical roadmap now](https://whimsical.com/roadmap-iced-7vhq6R35Lp3TmYH4WeYwLM)! Before diving into the roadmap, check out [the ecosystem overview] to get an idea of the current state of the library. [the ecosystem overview]: ECOSYSTEM.md - -## Next steps -Most of the work related to these features needs to happen in the __native__ path of the ecosystem, as the web already supports many of them. - -Once a step is completed, it is collapsed and added to this list: - - * [x] Scrollables / Clippables ([#24]) - * [x] Text input widget ([#25]) - * [x] TodoMVC example ([#26]) - * [x] Async actions ([#28]) - * [x] Custom layout engine ([#52]) - * [x] Event subscriptions ([#122]) - * [x] Custom styling ([#146]) - * [x] Canvas for 2D graphics ([#193]) - * [x] Basic overlay support ([#444]) - * [x] Animations [#31] - -[#24]: https://github.com/iced-rs/iced/issues/24 -[#25]: https://github.com/iced-rs/iced/issues/25 -[#26]: https://github.com/iced-rs/iced/issues/26 -[#28]: https://github.com/iced-rs/iced/issues/28 -[#52]: https://github.com/iced-rs/iced/pull/52 -[#122]: https://github.com/iced-rs/iced/pull/122 -[#146]: https://github.com/iced-rs/iced/pull/146 -[#193]: https://github.com/iced-rs/iced/pull/193 -[#444]: https://github.com/iced-rs/iced/pull/444 -[#31]: https://github.com/iced-rs/iced/issues/31 - -### Multi-window support ([#27]) -Open and control multiple windows at runtime. - -I think this could be achieved by implementing an additional trait in `iced_winit` similar to `Application` but with a slightly different `view` method, allowing users to control what is shown in each window. - -This approach should also allow us to perform custom optimizations for this particular use case. - -[#27]: https://github.com/iced-rs/iced/issues/27 - -### Canvas widget for 3D graphics (~~[#32]~~ [#343]) -A widget to draw freely in 3D. It could be used to draw charts, implement a Paint clone, a CAD application, etc. - -As a first approach, we could expose the underlying renderer directly here, and couple this widget with it ([`wgpu`] for now). Once [`wgpu`] gets WebGL or WebGPU support, this widget will be able to run on the web too. The renderer primitive could be a simple texture that the widget draws to. - -In the long run, we could expose a renderer-agnostic abstraction to perform the drawing. - -[#32]: https://github.com/iced-rs/iced/issues/32 -[#343]: https://github.com/iced-rs/iced/issues/343 - -### Text shaping and font fallback ([#33]) -[`wgpu_glyph`] uses [`glyph_brush`], which in turn uses [`rusttype`]. While the current implementation is able to layout text quite nicely, it does not perform any [text shaping]. - -[Text shaping] with font fallback is a necessary feature for any serious GUI toolkit. It unlocks support to truly localize your application, supporting many different scripts. - -The only available library that does a great job at shaping is [HarfBuzz], which is implemented in C++. [`skribo`] seems to be a nice [HarfBuzz] wrapper for Rust. - -This feature will probably imply rewriting [`wgpu_glyph`] entirely, as caching will be more complicated and the API will probably need to ask for more data. - -[#33]: https://github.com/iced-rs/iced/issues/33 -[`rusttype`]: https://github.com/redox-os/rusttype -[text shaping]: https://en.wikipedia.org/wiki/Complex_text_layout -[HarfBuzz]: https://github.com/harfbuzz/harfbuzz -[`skribo`]: https://github.com/linebender/skribo - -### Grid layout and text layout ([#34]) -Currently, `iced_native` only supports flexbox items. For instance, it is not possible to create a grid of items or make text float around an image. - -We will need to enhance the layouting engine to support different strategies and improve the way we measure text to lay it out in a more flexible way. - -[#34]: https://github.com/iced-rs/iced/issues/34 - -## Ideas that may be worth exploring - -### Reuse existing 2D renderers -While I believe [`wgpu`] has a great future ahead of it, implementing `iced_wgpu` and making it performant will definitely be a challenge. - -We should keep an eye on existing 2D graphic libraries, like [`piet`] or [`pathfinder`], and give them a try once/if they mature a bit more. - -The good news here is that most of Iced is renderer-agnostic, so changing the rendering strategy, if we deem it worth it, should be really easy. Also, a 2D graphics library will expose a higher-level API than [`wgpu`], so implementing a new renderer on top of it should be fairly straightforward. - -[`piet`]: https://github.com/linebender/piet -[`pathfinder`]: https://github.com/servo/pathfinder - -### Remove explicit state handling and lifetimes -Currently, `iced_native` forces users to provide the local state of each widget. While this could be considered a really pure form of describing a GUI, it makes some optimizations harder because of the borrow checker. - -The current borrow checker is not able to detect a drop was performed before reassigning a value to a mutable variable. Thus, keeping the generated widgets in `Application::view` alive between iterations of the event loop is not possible, which forces us to call this method quite often. `unsafe` could be used to workaround this, but it would feel fishy. - -We could take a different approach, and keep some kind of state tree decoupled from the actual widget definitions. This would force us to perform diffing of nodes, as the widgets will represent the desired state and not the whole state. - -Once the state lifetime of widgets is removed, we could keep them alive between iterations and even make `Application::view` take a non-mutable reference. This would also improve the end-user API, as it will not be necessary to constantly provide mutable state to widgets. - -This is a big undertaking and introduces a new set of problems. We should research and consider the implications of this approach in detail before going for it. - -### Try a different font rasterizer -[`wgpu_glyph`] depends indirectly on [`rusttype`]. We may be able to gain performance by using a different font rasterizer. [`fontdue`], for instance, has reported noticeable speedups. - -[`fontdue`]: https://github.com/mooman219/fontdue - -### Connect `iced_web` with `web-view` -It may be interesting to try to connect `iced_web` with [`web-view`]. It would give users a feature-complete renderer for free, and applications would still be leaner than with Electron. - -[`web-view`]: https://github.com/Boscop/web-view - -### Implement a lazy widget -Once we remove state lifetimes from widgets, we should be able to implement a widget storing a function that generates additional widgets. The runtime would then be able to control when to call this function and cache the generated widgets while some given value does not change. - -This could be very useful to build very performant user interfaces with a lot of different items. - -[Elm does it very well!](https://guide.elm-lang.org/optimization/lazy.html) - -[Elm]: https://elm-lang.org/ -[`winit`]: https://github.com/rust-windowing/winit -[`wgpu`]: https://github.com/gfx-rs/wgpu -[`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph -[`glyph_brush`]: https://github.com/alexheretic/glyph-brush From bc09901690cfb0ca319e9fe4d009378ee9f46cf8 Mon Sep 17 00:00:00 2001 From: Redhawk18 Date: Tue, 18 Jul 2023 14:09:33 -0400 Subject: [PATCH 022/321] Simplify contributing guidelines --- CONTRIBUTING.md | 2 +- README.md | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8782a2e3..412cf08f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Thank you for considering contributing to Iced! Feel free to read [the ecosystem overview] and [the roadmap] to get an idea of the current state of the library. -The main advice for new contributors is to share your ideas with the community. Introduce yourself over our [Discord server] or [start a discussion in an issue](https://github.com/iced-rs/iced/issues) explaining what you have in mind (do not be afraid of duplicated issues!). If you want to talk directly to me (@hecrj), you can also find me on Discord (`lone_scientist#9554`). +The main advice for new contributors is to share your ideas with the community. Introduce yourself over our [Discord server] or [start a discussion in an issue](https://github.com/iced-rs/iced/issues) explaining what you have in mind (do not be afraid of duplicated issues!). This is a very important step. It helps to coordinate work, get on the same page, and start building trust. Please, do not skip it! Remember that [Code is the Easy Part] and also [The Hard Parts of Open Source]! diff --git a/README.md b/README.md index c72fd770..e013246a 100644 --- a/README.md +++ b/README.md @@ -201,10 +201,8 @@ end-user-oriented GUI library, while keeping [the ecosystem] modular: Contributions are greatly appreciated! If you want to contribute, please read our [contributing guidelines] for more details. -Feedback is also welcome! You can open an issue or, if you want to talk, -come chat to our [Discord server]. Moreover, you can find me (and a bunch of -awesome folks) over the `#games-and-graphics` and `#gui-and-ui` channels in -the [Rust Community Discord]. I go by `lone_scientist#9554` there. +Feedback is also welcome! You can open a discussion or come chat to our +[Discord server]. ## Sponsors From 9537c9dfdaf09694fd05b7edd74729503e922696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 26 Jul 2023 20:27:33 +0200 Subject: [PATCH 023/321] Update `CHANGELOG` --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7174bb8..a2e900dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Quad rendering including border only inside of the bounds. [#1843](https://github.com/iced-rs/iced/pull/1843) - Redraw requests not being forwarded for `Component` overlays. [#1949](https://github.com/iced-rs/iced/pull/1949) - Blinking input cursor when window loses focus. [#1955](https://github.com/iced-rs/iced/pull/1955) +- Outdated `ROADMAP`. [#1958](https://github.com/iced-rs/iced/pull/1958) Many thanks to... @@ -91,6 +92,7 @@ Many thanks to... - @marienz - @nicksenger - @nicoburns +- @Redhawk18 - @RGBCube - @tarkah - @thunderstorm010 From dd5ef8b90895f626d4b8f0466c4457c5abf451a0 Mon Sep 17 00:00:00 2001 From: Joao Freitas <51237625+jhff@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:51:29 +0100 Subject: [PATCH 024/321] Add ComboBox widget - Widget implementation - Widget helper - Example --- examples/combo_box/Cargo.toml | 9 + examples/combo_box/README.md | 18 + examples/combo_box/src/main.rs | 143 +++++++ widget/src/combo_box.rs | 716 +++++++++++++++++++++++++++++++++ widget/src/helpers.rs | 18 + widget/src/lib.rs | 3 + 6 files changed, 907 insertions(+) create mode 100644 examples/combo_box/Cargo.toml create mode 100644 examples/combo_box/README.md create mode 100644 examples/combo_box/src/main.rs create mode 100644 widget/src/combo_box.rs diff --git a/examples/combo_box/Cargo.toml b/examples/combo_box/Cargo.toml new file mode 100644 index 00000000..be1b5e32 --- /dev/null +++ b/examples/combo_box/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "combo_box" +version = "0.1.0" +authors = ["Joao Freitas "] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } diff --git a/examples/combo_box/README.md b/examples/combo_box/README.md new file mode 100644 index 00000000..5fc87469 --- /dev/null +++ b/examples/combo_box/README.md @@ -0,0 +1,18 @@ +## Combo-Box + +A dropdown list of searchable and selectable options. + +It displays and positions an overlay based on the window position of the widget. + +The __[`main`]__ file contains all the code of the example. + +
+ +
+ +You can run it with `cargo run`: +``` +cargo run --package combo_box +``` + +[`main`]: src/main.rs diff --git a/examples/combo_box/src/main.rs b/examples/combo_box/src/main.rs new file mode 100644 index 00000000..22d05132 --- /dev/null +++ b/examples/combo_box/src/main.rs @@ -0,0 +1,143 @@ +use iced::widget::{ + column, combo_box, container, scrollable, text, vertical_space, +}; +use iced::{Alignment, Element, Length, Sandbox, Settings}; + +pub fn main() -> iced::Result { + Example::run(Settings::default()) +} + +struct Example { + languages: combo_box::State, + selected_language: Option, + text: String, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + LanguageSelected(Language), + LanguagePreview(Language), + LanguageBlurred, +} + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + Self { + languages: combo_box::State::new(Language::ALL.to_vec()), + selected_language: None, + text: String::new(), + } + } + + fn title(&self) -> String { + String::from("Combo box - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::LanguageSelected(language) => { + self.selected_language = Some(language); + self.text = language.hello().to_string(); + self.languages.unfocus(); + } + Message::LanguagePreview(language) => { + self.text = language.hello().to_string(); + } + Message::LanguageBlurred => { + self.text = self + .selected_language + .map(|language| language.hello().to_string()) + .unwrap_or_default(); + } + } + } + + fn view(&self) -> Element { + let combo_box = combo_box( + &self.languages, + "Type a language...", + self.selected_language.as_ref(), + Message::LanguageSelected, + ) + .on_selection(Message::LanguagePreview) + .on_blur(Message::LanguageBlurred) + .width(250); + + let content = column![ + "What is your language?", + combo_box, + vertical_space(150), + text(&self.text), + ] + .width(Length::Fill) + .align_items(Alignment::Center) + .spacing(10); + + container(scrollable(content)) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum Language { + Danish, + #[default] + English, + French, + German, + Italian, + Portuguese, + Spanish, + Other, +} + +impl Language { + const ALL: [Language; 8] = [ + Language::Danish, + Language::English, + Language::French, + Language::German, + Language::Italian, + Language::Portuguese, + Language::Spanish, + Language::Other, + ]; + + fn hello(&self) -> &str { + match self { + Language::Danish => "Halloy!", + Language::English => "Hello!", + Language::French => "Salut!", + Language::German => "Hallo!", + Language::Italian => "Ciao!", + Language::Portuguese => "Olá!", + Language::Spanish => "¡Hola!", + Language::Other => "... hello?", + } + } +} + +impl std::fmt::Display for Language { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Language::Danish => "Danish", + Language::English => "English", + Language::French => "French", + Language::German => "German", + Language::Italian => "Italian", + Language::Portuguese => "Portuguese", + Language::Spanish => "Spanish", + Language::Other => "Some other language", + } + ) + } +} diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs new file mode 100644 index 00000000..262f83a8 --- /dev/null +++ b/widget/src/combo_box.rs @@ -0,0 +1,716 @@ +//! Display a dropdown list of searchable and selectable options. +use crate::core::event::{self, Event}; +use crate::core::keyboard; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::text; +use crate::core::widget::{self, Widget}; +use crate::core::{Clipboard, Element, Length, Padding, Rectangle, Shell}; +use crate::overlay::menu; +use crate::text::LineHeight; +use crate::{container, scrollable, text_input, TextInput}; + +use std::cell::RefCell; +use std::fmt::Display; +use std::time::Instant; + +/// A widget for searching and selecting a single value from a list of options. +/// +/// This widget is composed by a [`TextInput`] that can be filled with the text +/// to search for corresponding values from the list of options that are displayed +/// as a [`Menu`]. +#[allow(missing_debug_implementations)] +pub struct ComboBox<'a, T, Message, Renderer = crate::Renderer> +where + Renderer: text::Renderer, + Renderer::Theme: text_input::StyleSheet + menu::StyleSheet, +{ + state: &'a State, + text_input: TextInput<'a, TextInputEvent, Renderer>, + font: Option, + selection: text_input::Value, + on_selected: Box Message>, + on_selection: Option Message>>, + on_blur: Option, + on_input: Option Message>>, + menu_style: ::Style, + padding: Padding, + size: Option, +} + +impl<'a, T, Message, Renderer> ComboBox<'a, T, Message, Renderer> +where + T: std::fmt::Display + Clone, + Renderer: text::Renderer, + Renderer::Theme: text_input::StyleSheet + menu::StyleSheet, +{ + /// Creates a new [`ComboBox`] with the given list of options, a placeholder, + /// the current selected value, and the message to produce when an option is + /// selected. + pub fn new( + state: &'a State, + placeholder: &str, + selection: Option<&T>, + on_selected: impl Fn(T) -> Message + 'static, + ) -> Self { + let text_input = TextInput::new(placeholder, &state.value()) + .on_input(TextInputEvent::TextChanged); + + let selection = selection.map(T::to_string).unwrap_or_else(String::new); + + Self { + state, + text_input, + font: None, + selection: text_input::Value::new(&selection), + on_selected: Box::new(on_selected), + on_selection: None, + on_input: None, + on_blur: None, + menu_style: Default::default(), + padding: Padding::new(0.0), + size: None, + } + } + + /// Sets the message that should be produced when some text is typed into + /// the [`TextInput`] of the [`ComboBox`]. + pub fn on_input( + mut self, + on_input: impl Fn(String) -> Message + 'static, + ) -> Self { + self.on_input = Some(Box::new(on_input)); + self + } + + /// Sets the message that will be produced when an option of the + /// [`ComboBox`] is hovered using the arrow keys. + pub fn on_selection( + mut self, + on_selection: impl Fn(T) -> Message + 'static, + ) -> Self { + self.on_selection = Some(Box::new(on_selection)); + self + } + + /// Sets the message that will be produced when the outside area + /// of the [`ComboBox`] is pressed. + pub fn on_blur(mut self, message: Message) -> Self { + self.on_blur = Some(message); + self + } + + /// Sets the [`Padding`] of the [`ComboBox`]. + pub fn padding(mut self, padding: impl Into) -> Self { + self.padding = padding.into(); + self.text_input = self.text_input.padding(self.padding); + self + } + + /// Sets the style of the [`ComboBox`]. + // TODO: Define its own `StyleSheet` trait + pub fn style(mut self, style: S) -> Self + where + S: Into<::Style> + + Into<::Style> + + Clone, + { + self.menu_style = style.clone().into(); + self.text_input = self.text_input.style(style); + self + } + + /// Sets the style of the [`TextInput`] of the [`ComboBox`]. + pub fn text_input_style(mut self, style: S) -> Self + where + S: Into<::Style> + Clone, + { + self.text_input = self.text_input.style(style); + self + } + + /// Sets the [`Font`] of the [`ComboBox`]. + pub fn font(mut self, font: Renderer::Font) -> Self { + self.text_input = self.text_input.font(font); + self.font = Some(font); + self + } + + /// Sets the [`Icon`] of the [`ComboBox`]. + pub fn icon(mut self, icon: text_input::Icon) -> Self { + self.text_input = self.text_input.icon(icon); + self + } + + /// Returns whether the [`ComboBox`] is currently focused or not. + pub fn is_focused(&self) -> bool { + self.state.is_focused() + } + + /// Sets the text sixe of the [`ComboBox`]. + pub fn size(mut self, size: f32) -> Self { + self.text_input = self.text_input.size(size); + self.size = Some(size); + self + } + + /// Sets the [`LineHeight`] of the [`ComboBox`]. + pub fn line_height(self, line_height: impl Into) -> Self { + Self { + text_input: self.text_input.line_height(line_height), + ..self + } + } + + /// Sets the width of the [`ComboBox`]. + pub fn width(self, width: impl Into) -> Self { + Self { + text_input: self.text_input.width(width), + ..self + } + } +} + +/// The local state of a [`ComboBox`]. +#[derive(Debug, Clone)] +pub struct State(RefCell>); + +#[derive(Debug, Clone)] +struct Inner { + text_input: text_input::State, + value: String, + options: Vec, + option_matchers: Vec, + filtered_options: Filtered, +} + +#[derive(Debug, Clone)] +struct Filtered { + options: Vec, + updated: Instant, +} + +impl State +where + T: Display + Clone, +{ + /// Creates a new [`State`] for a [`ComboBox`] with the given list of options. + pub fn new(options: Vec) -> Self { + Self::with_selection(options, None) + } + + /// Creates a new [`State`] for a [`ComboBox`] with the given list of options + /// and selected value. + pub fn with_selection(options: Vec, selection: Option<&T>) -> Self { + let value = selection.map(T::to_string).unwrap_or_else(String::new); + + // Pre-build "matcher" strings ahead of time so that search is fast + let option_matchers = build_matchers(&options); + + let filtered_options = Filtered::new( + search(&options, &option_matchers, &value) + .cloned() + .collect(), + ); + + Self(RefCell::new(Inner { + text_input: text_input::State::new(), + value, + options, + option_matchers, + filtered_options, + })) + } + + /// Focuses the [`ComboBox`]. + pub fn focused(self) -> Self { + self.focus(); + self + } + + /// Focuses the [`ComboBox`]. + pub fn focus(&self) { + let mut inner = self.0.borrow_mut(); + + inner.text_input.focus(); + } + + /// Unfocuses the [`ComboBox`]. + pub fn unfocus(&self) { + let mut inner = self.0.borrow_mut(); + + inner.text_input.unfocus(); + } + + /// Returns whether the [`ComboBox`] is currently focused or not. + pub fn is_focused(&self) -> bool { + let inner = self.0.borrow(); + + inner.text_input.is_focused() + } + + fn value(&self) -> String { + let inner = self.0.borrow(); + + inner.value.clone() + } + + fn text_input_tree(&self) -> widget::Tree { + let inner = self.0.borrow(); + + inner.text_input_tree() + } + + fn update_text_input(&self, tree: widget::Tree) { + let mut inner = self.0.borrow_mut(); + + inner.update_text_input(tree) + } + + fn with_inner(&self, f: impl FnOnce(&Inner) -> O) -> O { + let inner = self.0.borrow(); + + f(&inner) + } + + fn with_inner_mut(&self, f: impl FnOnce(&mut Inner)) { + let mut inner = self.0.borrow_mut(); + + f(&mut inner); + } + + fn sync_filtered_options(&self, options: &mut Filtered) { + let inner = self.0.borrow(); + + inner.filtered_options.sync(options); + } +} + +impl Inner { + fn text_input_tree(&self) -> widget::Tree { + widget::Tree { + tag: widget::tree::Tag::of::(), + state: widget::tree::State::new(self.text_input.clone()), + children: vec![], + } + } + + fn update_text_input(&mut self, tree: widget::Tree) { + self.text_input = + tree.state.downcast_ref::().clone(); + } +} + +impl Filtered +where + T: Clone, +{ + fn new(options: Vec) -> Self { + Self { + options, + updated: Instant::now(), + } + } + + fn empty() -> Self { + Self { + options: vec![], + updated: Instant::now(), + } + } + + fn update(&mut self, options: Vec) { + self.options = options; + self.updated = Instant::now(); + } + + fn sync(&self, other: &mut Filtered) { + if other.updated != self.updated { + *other = self.clone(); + } + } +} + +struct Menu { + menu: menu::State, + hovered_option: Option, + new_selection: Option, + filtered_options: Filtered, +} + +#[derive(Debug, Clone)] +enum TextInputEvent { + TextChanged(String), +} + +impl<'a, T, Message, Renderer> Widget + for ComboBox<'a, T, Message, Renderer> +where + T: Display + Clone + 'static, + Message: Clone, + Renderer: text::Renderer, + Renderer::Theme: container::StyleSheet + + text_input::StyleSheet + + scrollable::StyleSheet + + menu::StyleSheet, +{ + fn width(&self) -> Length { + Widget::::width(&self.text_input) + } + + fn height(&self) -> Length { + Widget::::height(&self.text_input) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.text_input.layout(renderer, limits) + } + + fn tag(&self) -> widget::tree::Tag { + widget::tree::Tag::of::>() + } + + fn state(&self) -> widget::tree::State { + widget::tree::State::new(Menu:: { + menu: menu::State::new(), + filtered_options: Filtered::empty(), + hovered_option: Some(0), + new_selection: None, + }) + } + + fn on_event( + &mut self, + tree: &mut widget::Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + let menu = tree.state.downcast_mut::>(); + + let started_focused = self.state.is_focused(); + // This is intended to check whether or not the message buffer was empty, + // since `Shell` does not expose such functionality. + let mut published_message_to_shell = false; + + // Create a new list of local messages + let mut local_messages = Vec::new(); + let mut local_shell = Shell::new(&mut local_messages); + + // Provide it to the widget + let mut tree = self.state.text_input_tree(); + let mut event_status = self.text_input.on_event( + &mut tree, + event.clone(), + layout, + cursor, + renderer, + clipboard, + &mut local_shell, + ); + self.state.update_text_input(tree); + + // Then finally react to them here + for message in local_messages { + let TextInputEvent::TextChanged(new_value) = message; + if let Some(on_input) = &self.on_input { + shell.publish((on_input)(new_value.clone())); + published_message_to_shell = true; + } + + // Couple the filtered options with the `ComboBox` + // value and only recompute them when the value changes, + // instead of doing it in every `view` call + self.state.with_inner_mut(|state| { + menu.hovered_option = Some(0); + state.value = new_value; + + state.filtered_options.update( + search( + &state.options, + &state.option_matchers, + &state.value, + ) + .cloned() + .collect(), + ); + }); + shell.invalidate_layout(); + } + + if self.state.is_focused() { + self.state.with_inner(|state| { + if let Event::Keyboard(keyboard::Event::KeyPressed { + key_code, + .. + }) = event + { + match key_code { + keyboard::KeyCode::Enter => { + if let Some(index) = &menu.hovered_option { + if let Some(option) = + state.filtered_options.options.get(*index) + { + menu.new_selection = Some(option.clone()); + } + } + + event_status = event::Status::Captured; + } + keyboard::KeyCode::Up => { + if let Some(index) = &mut menu.hovered_option { + *index = index.saturating_sub(1); + } else { + menu.hovered_option = Some(0); + } + + if let Some(on_selection) = &mut self.on_selection { + if let Some(option) = + menu.hovered_option.and_then(|index| { + state + .filtered_options + .options + .get(index) + }) + { + // Notify the selection + shell.publish((on_selection)( + option.clone(), + )); + published_message_to_shell = true; + } + } + + event_status = event::Status::Captured; + } + keyboard::KeyCode::Down => { + if let Some(index) = &mut menu.hovered_option { + *index = index.saturating_add(1).min( + state + .filtered_options + .options + .len() + .saturating_sub(1), + ); + } else { + menu.hovered_option = Some(0); + } + + if let Some(on_selection) = &mut self.on_selection { + if let Some(option) = + menu.hovered_option.and_then(|index| { + state + .filtered_options + .options + .get(index) + }) + { + // Notify the selection + shell.publish((on_selection)( + option.clone(), + )); + published_message_to_shell = true; + } + } + + event_status = event::Status::Captured; + } + _ => {} + } + } + }); + } + + // If the overlay menu has selected something + self.state.with_inner_mut(|state| { + if let Some(selection) = menu.new_selection.take() { + // Clear the value and reset the options and menu + state.value = String::new(); + state.filtered_options.update(state.options.clone()); + menu.menu = menu::State::default(); + + // Notify the selection + shell.publish((self.on_selected)(selection)); + published_message_to_shell = true; + + // Unfocus the input + let mut tree = state.text_input_tree(); + let _ = self.text_input.on_event( + &mut tree, + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )), + layout, + mouse::Cursor::Unavailable, + renderer, + clipboard, + &mut Shell::new(&mut vec![]), + ); + state.update_text_input(tree); + } + }); + + if started_focused + && !self.state.is_focused() + && !published_message_to_shell + { + if let Some(message) = self.on_blur.take() { + shell.publish(message); + } + } + + // Focus changed, invalidate widget tree to force a fresh `view` + if started_focused != self.state.is_focused() { + shell.invalidate_widgets(); + } + + event_status + } + + fn mouse_interaction( + &self, + _tree: &widget::Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let tree = self.state.text_input_tree(); + self.text_input + .mouse_interaction(&tree, layout, cursor, viewport, renderer) + } + + fn draw( + &self, + _tree: &widget::Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + _style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + ) { + let selection = if self.state.is_focused() || self.selection.is_empty() + { + None + } else { + Some(&self.selection) + }; + + let tree = self.state.text_input_tree(); + self.text_input + .draw(&tree, renderer, theme, layout, cursor, selection); + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut widget::Tree, + layout: Layout<'_>, + _renderer: &Renderer, + ) -> Option> { + let Menu { + menu, + filtered_options, + hovered_option, + .. + } = tree.state.downcast_mut::>(); + + if self.state.is_focused() { + let bounds = layout.bounds(); + + self.state.sync_filtered_options(filtered_options); + + let mut menu = menu::Menu::new( + menu, + &filtered_options.options, + hovered_option, + |x| (self.on_selected)(x), + ) + .width(bounds.width) + .padding(self.padding) + .style(self.menu_style.clone()); + + if let Some(font) = self.font { + menu = menu.font(font); + } + + if let Some(size) = self.size { + menu = menu.text_size(size); + } + + Some(menu.overlay(layout.position(), bounds.height)) + } else { + None + } + } +} + +impl<'a, T, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + T: Display + Clone + 'static, + Message: 'a + Clone, + Renderer: text::Renderer + 'a, + Renderer::Theme: container::StyleSheet + + text_input::StyleSheet + + scrollable::StyleSheet + + menu::StyleSheet, +{ + fn from(combo_box: ComboBox<'a, T, Message, Renderer>) -> Self { + Self::new(combo_box) + } +} + +/// Search list of options for a given query. +pub fn search<'a, T, A>( + options: impl IntoIterator + 'a, + option_matchers: impl IntoIterator + 'a, + query: &'a str, +) -> impl Iterator + 'a +where + A: AsRef + 'a, +{ + let query: Vec = query + .to_lowercase() + .split(|c: char| !c.is_ascii_alphanumeric()) + .map(String::from) + .collect(); + + options + .into_iter() + .zip(option_matchers.into_iter()) + // Make sure each part of the query is found in the option + .filter_map(move |(option, matcher)| { + if query.iter().all(|part| matcher.as_ref().contains(part)) { + Some(option) + } else { + None + } + }) +} + +/// Build matchers from given list of options. +pub fn build_matchers<'a, T>( + options: impl IntoIterator + 'a, +) -> Vec +where + T: Display + 'a, +{ + options + .into_iter() + .map(|opt| { + let mut matcher = opt.to_string(); + matcher.retain(|c| c.is_ascii_alphanumeric()); + matcher.to_lowercase() + }) + .collect() +} diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 3f5136f8..9c3c83a9 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -1,6 +1,7 @@ //! Helper functions to create pure widgets. use crate::button::{self, Button}; use crate::checkbox::{self, Checkbox}; +use crate::combo_box::{self, ComboBox}; use crate::container::{self, Container}; use crate::core; use crate::core::widget::operation; @@ -252,6 +253,23 @@ where PickList::new(options, selected, on_selected) } +/// Creates a new [`ComboBox`]. +/// +/// [`ComboBox`]: widget::ComboBox +pub fn combo_box<'a, T, Message, Renderer>( + state: &'a combo_box::State, + placeholder: &str, + selection: Option<&T>, + on_selected: impl Fn(T) -> Message + 'static, +) -> ComboBox<'a, T, Message, Renderer> +where + T: std::fmt::Display + Clone, + Renderer: core::text::Renderer, + Renderer::Theme: text_input::StyleSheet + overlay::menu::StyleSheet, +{ + ComboBox::new(state, placeholder, selection, on_selected) +} + /// Creates a new horizontal [`Space`] with the given [`Length`]. /// /// [`Space`]: widget::Space diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 9da13f9b..316e8829 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -27,6 +27,7 @@ mod row; pub mod button; pub mod checkbox; +pub mod combo_box; pub mod container; pub mod overlay; pub mod pane_grid; @@ -63,6 +64,8 @@ pub use checkbox::Checkbox; #[doc(no_inline)] pub use column::Column; #[doc(no_inline)] +pub use combo_box::ComboBox; +#[doc(no_inline)] pub use container::Container; #[doc(no_inline)] pub use mouse_area::MouseArea; From 470e13c806f4239ca3f2e90e9c0a794a81e354d8 Mon Sep 17 00:00:00 2001 From: Joao Freitas <51237625+jhff@users.noreply.github.com> Date: Thu, 13 Jul 2023 14:18:57 +0100 Subject: [PATCH 025/321] Add gif to example --- examples/combo_box/README.md | 2 +- examples/combo_box/combobox.gif | Bin 0 -> 1414629 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 examples/combo_box/combobox.gif diff --git a/examples/combo_box/README.md b/examples/combo_box/README.md index 5fc87469..9cd224ad 100644 --- a/examples/combo_box/README.md +++ b/examples/combo_box/README.md @@ -7,7 +7,7 @@ It displays and positions an overlay based on the window position of the widget. The __[`main`]__ file contains all the code of the example.
- +
You can run it with `cargo run`: diff --git a/examples/combo_box/combobox.gif b/examples/combo_box/combobox.gif new file mode 100644 index 0000000000000000000000000000000000000000..f216c026dd60e5fee34999c120bd1551588ae233 GIT binary patch literal 1414629 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 GDvl4Xi>|R?h{KWDNR+6vhzlSt5=deG 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±vSG_*#We{en9MjP3FtocLS>ggMWYzex>3`||A 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