From 11287c882e23e1f3081d06bf5ff3e99a02ef030a Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Wed, 12 Jul 2023 16:30:12 -0700 Subject: 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 { -- cgit From a9987cb32e4d65b83f134ba54f51dffe16e93a50 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 13 Jul 2023 02:53:45 +0200 Subject: 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 { -- cgit From d36758405789d6a305d978eefb46a1ca90d141d2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 13 Jul 2023 03:01:53 +0200 Subject: 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, } } -- cgit From dc0ebdc525dcb234fb754248eb1ee1606f91e839 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 13 Jul 2023 16:40:47 +0200 Subject: 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) }; -- cgit From 66d671066386cd4ec1addbdfe0750e3077a5ea51 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 13 Jul 2023 12:10:10 -0700 Subject: 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(); -- cgit From 44c07323067fa8c09122356c111047082d946c59 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 13 Jul 2023 12:21:24 -0700 Subject: 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)) => { -- cgit From 42c423b4a89613c4e1c552c891c1391a34837122 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 15 Jul 2023 10:04:25 -0700 Subject: 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, -- cgit From 189817594f3aa8db0c2ac00b3d4128ae7733da45 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 20 Jul 2023 20:39:49 +0200 Subject: 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"])) -- cgit From 25936e441952994c7791b0a3ac907ab26c3c012e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 20 Jul 2023 20:44:14 +0200 Subject: 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) -- cgit From b6bee55fa23d5f205d4cb6ed7bfde60c60b84373 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 20 Jul 2023 20:44:32 +0200 Subject: 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) -- cgit From 95ff96f71f8f8069608ad08985e2b1214dd42284 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 7 Jul 2023 07:12:37 +0200 Subject: 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... } } } -- cgit From 1006206fb260e6382ddf50a543846ccb9ffa957b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 20 Jul 2023 20:48:39 +0200 Subject: 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" -- cgit From 2a05ef9601bca560e68f9a16ff1875cfed33e0ea Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Mon, 24 Jul 2023 09:26:24 -0700 Subject: 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() -- cgit From 355ef8188a34d63eb0d93bd8b3329f6a15e68e59 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Jul 2023 02:09:16 +0200 Subject: 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 }}' -- cgit From 269e5410da8a9d04b62671042167ea475283d678 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Jul 2023 02:19:25 +0200 Subject: 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 -- cgit From d9faf4c9808f9959ac3dcba052c4c2febd1d0481 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Jul 2023 02:19:44 +0200 Subject: 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 }}' -- cgit From 0be3fe4ec760dd4eba1b753b87b6f89c61d29747 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Jul 2023 02:24:22 +0200 Subject: 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: | -- cgit From 6531f2789702d3490b3e7b74686c03954fd14a04 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Jul 2023 02:30:37 +0200 Subject: 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) -- cgit From 94e991a785eddcc1c706a5a7111e1e88b18aef41 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Fri, 21 Jul 2023 13:57:49 -0700 Subject: 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, +} -- cgit From 13c8d965d3295c83a6b77b1831db1139833a5b70 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Jul 2023 20:15:49 +0200 Subject: 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) -- cgit From 9afcf067dbed26b6640dde8de9c24f90eea5c3c1 Mon Sep 17 00:00:00 2001 From: Redhawk18 Date: Tue, 18 Jul 2023 14:09:13 -0400 Subject: 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 -- cgit From bc09901690cfb0ca319e9fe4d009378ee9f46cf8 Mon Sep 17 00:00:00 2001 From: Redhawk18 Date: Tue, 18 Jul 2023 14:09:33 -0400 Subject: 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 -- cgit From 9537c9dfdaf09694fd05b7edd74729503e922696 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Jul 2023 20:27:33 +0200 Subject: 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 -- cgit 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: 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; -- cgit 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: 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 00000000..f216c026 Binary files /dev/null and b/examples/combo_box/combobox.gif differ -- cgit From 7fe3389cf10d5ca3752127df30dafbd6965a0fe9 Mon Sep 17 00:00:00 2001 From: Joao Freitas <51237625+jhff@users.noreply.github.com> Date: Thu, 13 Jul 2023 14:30:54 +0100 Subject: Swap unwrap_or_else to unwrap_or_default --- widget/src/combo_box.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 262f83a8..23839980 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -58,7 +58,7 @@ where let text_input = TextInput::new(placeholder, &state.value()) .on_input(TextInputEvent::TextChanged); - let selection = selection.map(T::to_string).unwrap_or_else(String::new); + let selection = selection.map(T::to_string).unwrap_or_default(); Self { state, @@ -204,7 +204,7 @@ where /// 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); + let value = selection.map(T::to_string).unwrap_or_default(); // Pre-build "matcher" strings ahead of time so that search is fast let option_matchers = build_matchers(&options); -- cgit From 14fb723eecdf3ee667874882210ac826aa700919 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Jul 2023 22:01:17 +0200 Subject: Add `Viewport` to `on_event` for `ComboBox` --- widget/src/combo_box.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 23839980..956a470a 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -394,6 +394,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + viewport: &Rectangle, ) -> event::Status { let menu = tree.state.downcast_mut::>(); @@ -416,6 +417,7 @@ where renderer, clipboard, &mut local_shell, + viewport, ); self.state.update_text_input(tree); @@ -554,6 +556,7 @@ where renderer, clipboard, &mut Shell::new(&mut vec![]), + viewport, ); state.update_text_input(tree); } -- cgit From 9eb2889d09e42b250f12be9ba9ef8a470d8eeeae Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Jul 2023 22:33:33 +0200 Subject: Use default padding of `TextInput` in `ComboBox` --- widget/src/combo_box.rs | 2 +- widget/src/text_input.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 956a470a..91a91c32 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -70,7 +70,7 @@ where on_input: None, on_blur: None, menu_style: Default::default(), - padding: Padding::new(0.0), + padding: text_input::DEFAULT_PADDING, size: None, } } diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 9958cbcc..b899eb67 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -76,6 +76,9 @@ where style: ::Style, } +/// The default [`Padding`] of a [`TextInput`]. +pub const DEFAULT_PADDING: Padding = Padding::new(5.0); + impl<'a, Message, Renderer> TextInput<'a, Message, Renderer> where Message: Clone, @@ -95,7 +98,7 @@ where is_secure: false, font: None, width: Length::Fill, - padding: Padding::new(5.0), + padding: DEFAULT_PADDING, size: None, line_height: text::LineHeight::default(), on_input: None, -- cgit From 28d32a8b6463b5756aa7cc497c1e26e173f70bee Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Jul 2023 22:34:56 +0200 Subject: Fix `on_option_hovered` support in `ComboBox` --- examples/combo_box/src/main.rs | 20 ++++++++++---------- widget/src/combo_box.rs | 42 +++++++++++++++++++++++++++++++----------- widget/src/overlay/menu.rs | 23 +++++++++++++++++++++-- widget/src/pick_list.rs | 1 + 4 files changed, 63 insertions(+), 23 deletions(-) diff --git a/examples/combo_box/src/main.rs b/examples/combo_box/src/main.rs index 22d05132..2e6f95d5 100644 --- a/examples/combo_box/src/main.rs +++ b/examples/combo_box/src/main.rs @@ -15,9 +15,9 @@ struct Example { #[derive(Debug, Clone, Copy)] enum Message { - LanguageSelected(Language), - LanguagePreview(Language), - LanguageBlurred, + Selected(Language), + OptionHovered(Language), + Closed, } impl Sandbox for Example { @@ -37,15 +37,15 @@ impl Sandbox for Example { fn update(&mut self, message: Message) { match message { - Message::LanguageSelected(language) => { + Message::Selected(language) => { self.selected_language = Some(language); self.text = language.hello().to_string(); self.languages.unfocus(); } - Message::LanguagePreview(language) => { + Message::OptionHovered(language) => { self.text = language.hello().to_string(); } - Message::LanguageBlurred => { + Message::Closed => { self.text = self .selected_language .map(|language| language.hello().to_string()) @@ -59,17 +59,17 @@ impl Sandbox for Example { &self.languages, "Type a language...", self.selected_language.as_ref(), - Message::LanguageSelected, + Message::Selected, ) - .on_selection(Message::LanguagePreview) - .on_blur(Message::LanguageBlurred) + .on_option_hovered(Message::OptionHovered) + .on_close(Message::Closed) .width(250); let content = column![ + text(&self.text), "What is your language?", combo_box, vertical_space(150), - text(&self.text), ] .width(Length::Fill) .align_items(Alignment::Center) diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 91a91c32..14fe2528 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -32,8 +32,8 @@ where font: Option, selection: text_input::Value, on_selected: Box Message>, - on_selection: Option Message>>, - on_blur: Option, + on_option_hovered: Option Message>>, + on_close: Option, on_input: Option Message>>, menu_style: ::Style, padding: Padding, @@ -66,9 +66,9 @@ where font: None, selection: text_input::Value::new(&selection), on_selected: Box::new(on_selected), - on_selection: None, + on_option_hovered: None, on_input: None, - on_blur: None, + on_close: None, menu_style: Default::default(), padding: text_input::DEFAULT_PADDING, size: None, @@ -87,18 +87,18 @@ where /// Sets the message that will be produced when an option of the /// [`ComboBox`] is hovered using the arrow keys. - pub fn on_selection( + pub fn on_option_hovered( mut self, on_selection: impl Fn(T) -> Message + 'static, ) -> Self { - self.on_selection = Some(Box::new(on_selection)); + self.on_option_hovered = 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); + pub fn on_close(mut self, message: Message) -> Self { + self.on_close = Some(message); self } @@ -424,6 +424,7 @@ where // 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; @@ -451,6 +452,20 @@ where if self.state.is_focused() { self.state.with_inner(|state| { + if !started_focused { + if let Some(on_option_hovered) = &mut self.on_option_hovered + { + let hovered_option = menu.hovered_option.unwrap_or(0); + + if let Some(option) = + state.filtered_options.options.get(hovered_option) + { + shell.publish(on_option_hovered(option.clone())); + published_message_to_shell = true; + } + } + } + if let Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. @@ -475,7 +490,9 @@ where menu.hovered_option = Some(0); } - if let Some(on_selection) = &mut self.on_selection { + if let Some(on_selection) = + &mut self.on_option_hovered + { if let Some(option) = menu.hovered_option.and_then(|index| { state @@ -507,7 +524,9 @@ where menu.hovered_option = Some(0); } - if let Some(on_selection) = &mut self.on_selection { + if let Some(on_selection) = + &mut self.on_option_hovered + { if let Some(option) = menu.hovered_option.and_then(|index| { state @@ -566,7 +585,7 @@ where && !self.state.is_focused() && !published_message_to_shell { - if let Some(message) = self.on_blur.take() { + if let Some(message) = self.on_close.take() { shell.publish(message); } } @@ -637,6 +656,7 @@ where &filtered_options.options, hovered_option, |x| (self.on_selected)(x), + self.on_option_hovered.as_deref(), ) .width(bounds.width) .padding(self.padding) diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 72662422..f7bdeef6 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -28,6 +28,7 @@ where options: &'a [T], hovered_option: &'a mut Option, on_selected: Box Message + 'a>, + on_option_hovered: Option<&'a dyn Fn(T) -> Message>, width: f32, padding: Padding, text_size: Option, @@ -52,12 +53,14 @@ where options: &'a [T], hovered_option: &'a mut Option, on_selected: impl FnMut(T) -> Message + 'a, + on_option_hovered: Option<&'a dyn Fn(T) -> Message>, ) -> Self { Menu { state, options, hovered_option, on_selected: Box::new(on_selected), + on_option_hovered, width: 0.0, padding: Padding::ZERO, text_size: None, @@ -187,6 +190,7 @@ where options, hovered_option, on_selected, + on_option_hovered, width, padding, font, @@ -200,6 +204,7 @@ where options, hovered_option, on_selected, + on_option_hovered, font, text_size, text_line_height, @@ -321,6 +326,7 @@ where options: &'a [T], hovered_option: &'a mut Option, on_selected: Box Message + 'a>, + on_option_hovered: Option<&'a dyn Fn(T) -> Message>, padding: Padding, text_size: Option, text_line_height: text::LineHeight, @@ -405,8 +411,21 @@ where self.text_line_height.to_absolute(Pixels(text_size)), ) + self.padding.vertical(); - *self.hovered_option = - Some((cursor_position.y / option_height) as usize); + let new_hovered_option = + (cursor_position.y / option_height) as usize; + + if let Some(on_option_hovered) = self.on_option_hovered { + if *self.hovered_option != Some(new_hovered_option) { + if let Some(option) = + self.options.get(new_hovered_option) + { + shell + .publish(on_option_hovered(option.clone())); + } + } + } + + *self.hovered_option = Some(new_hovered_option); } } Event::Touch(touch::Event::FingerPressed { .. }) => { diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index d99ada10..0a1e2a99 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -566,6 +566,7 @@ where (on_selected)(option) }, + None, ) .width(bounds.width) .padding(padding) -- cgit From 559ebdbb3a0b90ff28f361f5466f17178b6d8137 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Jul 2023 22:35:42 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3036af4f..d6a989f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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) +- `ComboBox` widget. [#1954](https://github.com/iced-rs/iced/pull/1954) - `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) -- cgit From e29754f32d03efc4b075e9b63cc554d128bc2ccf Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Jul 2023 22:36:50 +0200 Subject: Rename `on_selection` to `on_option_hovered` in `combo_box` --- widget/src/combo_box.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 14fe2528..93fc92b9 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -89,9 +89,9 @@ where /// [`ComboBox`] is hovered using the arrow keys. pub fn on_option_hovered( mut self, - on_selection: impl Fn(T) -> Message + 'static, + on_option_hovered: impl Fn(T) -> Message + 'static, ) -> Self { - self.on_option_hovered = Some(Box::new(on_selection)); + self.on_option_hovered = Some(Box::new(on_option_hovered)); self } @@ -490,7 +490,7 @@ where menu.hovered_option = Some(0); } - if let Some(on_selection) = + if let Some(on_option_hovered) = &mut self.on_option_hovered { if let Some(option) = @@ -502,7 +502,7 @@ where }) { // Notify the selection - shell.publish((on_selection)( + shell.publish((on_option_hovered)( option.clone(), )); published_message_to_shell = true; @@ -524,7 +524,7 @@ where menu.hovered_option = Some(0); } - if let Some(on_selection) = + if let Some(on_option_hovered) = &mut self.on_option_hovered { if let Some(option) = @@ -536,7 +536,7 @@ where }) { // Notify the selection - shell.publish((on_selection)( + shell.publish((on_option_hovered)( option.clone(), )); published_message_to_shell = true; -- cgit From e2ba7ece83f141c149659747977147392df008f4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 27 Jul 2023 01:02:28 +0200 Subject: Introduce `visible_bounds` operation for `Container` --- core/src/element.rs | 11 ++-- core/src/overlay/element.rs | 7 ++- core/src/overlay/group.rs | 2 +- core/src/rectangle.rs | 15 ++++++ core/src/widget/operation.rs | 32 +++++++++--- core/src/widget/operation/focusable.rs | 6 +++ core/src/widget/operation/scrollable.rs | 19 ++++++- core/src/widget/operation/text_input.rs | 5 ++ examples/toast/src/main.rs | 4 +- widget/src/button.rs | 2 +- widget/src/column.rs | 2 +- widget/src/container.rs | 93 ++++++++++++++++++++++++++++++++- widget/src/lazy/component.rs | 10 ++-- widget/src/pane_grid.rs | 2 +- widget/src/row.rs | 2 +- widget/src/scrollable.rs | 14 ++++- 16 files changed, 201 insertions(+), 25 deletions(-) diff --git a/core/src/element.rs b/core/src/element.rs index b9b76247..d2c6358b 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -5,7 +5,9 @@ use crate::overlay; use crate::renderer; use crate::widget; use crate::widget::tree::{self, Tree}; -use crate::{Clipboard, Color, Layout, Length, Rectangle, Shell, Widget}; +use crate::{ + Clipboard, Color, Layout, Length, Rectangle, Shell, Vector, Widget, +}; use std::any::Any; use std::borrow::Borrow; @@ -325,11 +327,12 @@ where fn container( &mut self, id: Option<&widget::Id>, + bounds: Rectangle, operate_on_children: &mut dyn FnMut( &mut dyn widget::Operation, ), ) { - self.operation.container(id, &mut |operation| { + self.operation.container(id, bounds, &mut |operation| { operate_on_children(&mut MapOperation { operation }); }); } @@ -346,8 +349,10 @@ where &mut self, state: &mut dyn widget::operation::Scrollable, id: Option<&widget::Id>, + bounds: Rectangle, + translation: Vector, ) { - self.operation.scrollable(state, id); + self.operation.scrollable(state, id, bounds, translation); } fn text_input( diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index c2134343..29b404b8 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -172,11 +172,12 @@ where fn container( &mut self, id: Option<&widget::Id>, + bounds: Rectangle, operate_on_children: &mut dyn FnMut( &mut dyn widget::Operation, ), ) { - self.operation.container(id, &mut |operation| { + self.operation.container(id, bounds, &mut |operation| { operate_on_children(&mut MapOperation { operation }); }); } @@ -193,8 +194,10 @@ where &mut self, state: &mut dyn widget::operation::Scrollable, id: Option<&widget::Id>, + bounds: Rectangle, + translation: Vector, ) { - self.operation.scrollable(state, id); + self.operation.scrollable(state, id, bounds, translation); } fn text_input( diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index deffaad0..691686cd 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -138,7 +138,7 @@ where renderer: &Renderer, operation: &mut dyn widget::Operation, ) { - operation.container(None, &mut |operation| { + operation.container(None, layout.bounds(), &mut |operation| { self.children.iter_mut().zip(layout.children()).for_each( |(child, layout)| { child.operate(layout, renderer, operation); diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 7ff324cb..db56aa18 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -197,3 +197,18 @@ where } } } + +impl std::ops::Sub> for Rectangle +where + T: std::ops::Sub, +{ + type Output = Rectangle; + + fn sub(self, translation: Vector) -> Self { + Rectangle { + x: self.x - translation.x, + y: self.y - translation.y, + ..self + } + } +} diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs index ad188c36..b91cf9ac 100644 --- a/core/src/widget/operation.rs +++ b/core/src/widget/operation.rs @@ -8,6 +8,7 @@ pub use scrollable::Scrollable; pub use text_input::TextInput; use crate::widget::Id; +use crate::{Rectangle, Vector}; use std::any::Any; use std::fmt; @@ -23,6 +24,7 @@ pub trait Operation { fn container( &mut self, id: Option<&Id>, + bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ); @@ -30,7 +32,14 @@ pub trait Operation { fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {} /// Operates on a widget that can be scrolled. - fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {} + fn scrollable( + &mut self, + _state: &mut dyn Scrollable, + _id: Option<&Id>, + _bounds: Rectangle, + _translation: Vector, + ) { + } /// Operates on a widget that has text input. fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {} @@ -92,6 +101,7 @@ where fn container( &mut self, id: Option<&Id>, + bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { struct MapRef<'a, A> { @@ -102,11 +112,12 @@ where fn container( &mut self, id: Option<&Id>, + bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { let Self { operation, .. } = self; - operation.container(id, &mut |operation| { + operation.container(id, bounds, &mut |operation| { operate_on_children(&mut MapRef { operation }); }); } @@ -115,8 +126,10 @@ where &mut self, state: &mut dyn Scrollable, id: Option<&Id>, + bounds: Rectangle, + translation: Vector, ) { - self.operation.scrollable(state, id); + self.operation.scrollable(state, id, bounds, translation); } fn focusable( @@ -145,15 +158,21 @@ where MapRef { operation: operation.as_mut(), } - .container(id, operate_on_children); + .container(id, bounds, operate_on_children); } fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { self.operation.focusable(state, id); } - fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { - self.operation.scrollable(state, id); + fn scrollable( + &mut self, + state: &mut dyn Scrollable, + id: Option<&Id>, + bounds: Rectangle, + translation: Vector, + ) { + self.operation.scrollable(state, id, bounds, translation); } fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { @@ -197,6 +216,7 @@ pub fn scope( fn container( &mut self, id: Option<&Id>, + _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { if id == Some(&self.target) { diff --git a/core/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs index 312e4894..ab1b677e 100644 --- a/core/src/widget/operation/focusable.rs +++ b/core/src/widget/operation/focusable.rs @@ -1,6 +1,7 @@ //! Operate on widgets that can be focused. use crate::widget::operation::{Operation, Outcome}; use crate::widget::Id; +use crate::Rectangle; /// The internal state of a widget that can be focused. pub trait Focusable { @@ -45,6 +46,7 @@ pub fn focus(target: Id) -> impl Operation { fn container( &mut self, _id: Option<&Id>, + _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { operate_on_children(self) @@ -80,6 +82,7 @@ where fn container( &mut self, _id: Option<&Id>, + _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { operate_on_children(self) @@ -126,6 +129,7 @@ pub fn focus_previous() -> impl Operation { fn container( &mut self, _id: Option<&Id>, + _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { operate_on_children(self) @@ -159,6 +163,7 @@ pub fn focus_next() -> impl Operation { fn container( &mut self, _id: Option<&Id>, + _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { operate_on_children(self) @@ -185,6 +190,7 @@ pub fn find_focused() -> impl Operation { fn container( &mut self, _id: Option<&Id>, + _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { operate_on_children(self) diff --git a/core/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs index f947344d..4f8b2a98 100644 --- a/core/src/widget/operation/scrollable.rs +++ b/core/src/widget/operation/scrollable.rs @@ -1,5 +1,6 @@ //! Operate on widgets that can be scrolled. use crate::widget::{Id, Operation}; +use crate::{Rectangle, Vector}; /// The internal state of a widget that can be scrolled. pub trait Scrollable { @@ -22,12 +23,19 @@ pub fn snap_to(target: Id, offset: RelativeOffset) -> impl Operation { fn container( &mut self, _id: Option<&Id>, + _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { operate_on_children(self) } - fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { + fn scrollable( + &mut self, + state: &mut dyn Scrollable, + id: Option<&Id>, + _bounds: Rectangle, + _translation: Vector, + ) { if Some(&self.target) == id { state.snap_to(self.offset); } @@ -49,12 +57,19 @@ pub fn scroll_to(target: Id, offset: AbsoluteOffset) -> impl Operation { fn container( &mut self, _id: Option<&Id>, + _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { operate_on_children(self) } - fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { + fn scrollable( + &mut self, + state: &mut dyn Scrollable, + id: Option<&Id>, + _bounds: Rectangle, + _translation: Vector, + ) { if Some(&self.target) == id { state.scroll_to(self.offset); } diff --git a/core/src/widget/operation/text_input.rs b/core/src/widget/operation/text_input.rs index 4c773e99..a9ea2e81 100644 --- a/core/src/widget/operation/text_input.rs +++ b/core/src/widget/operation/text_input.rs @@ -1,6 +1,7 @@ //! Operate on widgets that have text input. use crate::widget::operation::Operation; use crate::widget::Id; +use crate::Rectangle; /// The internal state of a widget that has text input. pub trait TextInput { @@ -34,6 +35,7 @@ pub fn move_cursor_to_front(target: Id) -> impl Operation { fn container( &mut self, _id: Option<&Id>, + _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { operate_on_children(self) @@ -63,6 +65,7 @@ pub fn move_cursor_to_end(target: Id) -> impl Operation { fn container( &mut self, _id: Option<&Id>, + _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { operate_on_children(self) @@ -93,6 +96,7 @@ pub fn move_cursor_to(target: Id, position: usize) -> impl Operation { fn container( &mut self, _id: Option<&Id>, + _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { operate_on_children(self) @@ -121,6 +125,7 @@ pub fn select_all(target: Id) -> impl Operation { fn container( &mut self, _id: Option<&Id>, + _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { operate_on_children(self) diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 5d29e895..42f6c348 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -381,7 +381,7 @@ mod toast { renderer: &Renderer, operation: &mut dyn Operation, ) { - operation.container(None, &mut |operation| { + operation.container(None, layout.bounds(), &mut |operation| { self.content.as_widget().operate( &mut state.children[0], layout, @@ -622,7 +622,7 @@ mod toast { renderer: &Renderer, operation: &mut dyn widget::Operation, ) { - operation.container(None, &mut |operation| { + operation.container(None, layout.bounds(), &mut |operation| { self.toasts .iter() .zip(self.state.iter_mut()) diff --git a/widget/src/button.rs b/widget/src/button.rs index 1312095f..5727c631 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -181,7 +181,7 @@ where renderer: &Renderer, operation: &mut dyn Operation, ) { - operation.container(None, &mut |operation| { + operation.container(None, layout.bounds(), &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], layout.children().next().unwrap(), diff --git a/widget/src/column.rs b/widget/src/column.rs index 9271d5ef..c16477f3 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -148,7 +148,7 @@ where renderer: &Renderer, operation: &mut dyn Operation, ) { - operation.container(None, &mut |operation| { + operation.container(None, layout.bounds(), &mut |operation| { self.children .iter() .zip(&mut tree.children) diff --git a/widget/src/container.rs b/widget/src/container.rs index 64cf5cd5..1f1df861 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -8,8 +8,9 @@ use crate::core::renderer; use crate::core::widget::{self, Operation, Tree}; use crate::core::{ Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels, - Point, Rectangle, Shell, Widget, + Point, Rectangle, Shell, Size, Vector, Widget, }; +use crate::runtime::Command; pub use iced_style::container::{Appearance, StyleSheet}; @@ -180,6 +181,7 @@ where ) { operation.container( self.id.as_ref().map(|id| &id.0), + layout.bounds(), &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], @@ -368,3 +370,92 @@ impl From for widget::Id { id.0 } } + +/// Produces a [`Command`] that queries the visible screen bounds of the +/// [`Container`] with the given [`Id`]. +pub fn visible_bounds(id: Id) -> Command> { + struct VisibleBounds { + target: widget::Id, + depth: usize, + scrollables: Vec<(Vector, Rectangle, usize)>, + bounds: Option, + } + + impl Operation> for VisibleBounds { + fn scrollable( + &mut self, + _state: &mut dyn widget::operation::Scrollable, + _id: Option<&widget::Id>, + bounds: Rectangle, + translation: Vector, + ) { + match self.scrollables.last() { + Some((last_translation, last_viewport, _depth)) => { + let viewport = last_viewport + .intersection(&(bounds - *last_translation)) + .unwrap_or(Rectangle::new(Point::ORIGIN, Size::ZERO)); + + self.scrollables.push(( + translation + *last_translation, + viewport, + self.depth, + )); + } + None => { + self.scrollables.push((translation, bounds, self.depth)); + } + } + } + + fn container( + &mut self, + id: Option<&widget::Id>, + bounds: Rectangle, + operate_on_children: &mut dyn FnMut( + &mut dyn Operation>, + ), + ) { + if self.bounds.is_some() { + return; + } + + if id == Some(&self.target) { + match self.scrollables.last() { + Some((translation, viewport, _)) => { + self.bounds = + viewport.intersection(&(bounds - *translation)); + } + None => { + self.bounds = Some(bounds); + } + } + + return; + } + + self.depth += 1; + + operate_on_children(self); + + self.depth -= 1; + + match self.scrollables.last() { + Some((_, _, depth)) if self.depth == *depth => { + let _ = self.scrollables.pop(); + } + _ => {} + } + } + + fn finish(&self) -> widget::operation::Outcome> { + widget::operation::Outcome::Some(self.bounds) + } + } + + Command::widget(VisibleBounds { + target: id.into(), + depth: 0, + scrollables: Vec::new(), + bounds: None, + }) +} diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index bc0e23df..19df2792 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -7,7 +7,8 @@ use crate::core::renderer; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, + self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, + Widget, }; use crate::runtime::overlay::Nested; @@ -340,11 +341,12 @@ where fn container( &mut self, id: Option<&widget::Id>, + bounds: Rectangle, operate_on_children: &mut dyn FnMut( &mut dyn widget::Operation, ), ) { - self.operation.container(id, &mut |operation| { + self.operation.container(id, bounds, &mut |operation| { operate_on_children(&mut MapOperation { operation }); }); } @@ -369,8 +371,10 @@ where &mut self, state: &mut dyn widget::operation::Scrollable, id: Option<&widget::Id>, + bounds: Rectangle, + translation: Vector, ) { - self.operation.scrollable(state, id); + self.operation.scrollable(state, id, bounds, translation); } fn custom( diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 4f6dfbe8..0f4ab9eb 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -297,7 +297,7 @@ where renderer: &Renderer, operation: &mut dyn widget::Operation, ) { - operation.container(None, &mut |operation| { + operation.container(None, layout.bounds(), &mut |operation| { self.contents .iter() .zip(&mut tree.children) diff --git a/widget/src/row.rs b/widget/src/row.rs index 7baaaae3..99b2a0bf 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -137,7 +137,7 @@ where renderer: &Renderer, operation: &mut dyn Operation, ) { - operation.container(None, &mut |operation| { + operation.container(None, layout.bounds(), &mut |operation| { self.children .iter() .zip(&mut tree.children) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index f621fb26..103e3944 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -254,10 +254,22 @@ where ) { let state = tree.state.downcast_mut::(); - operation.scrollable(state, self.id.as_ref().map(|id| &id.0)); + let bounds = layout.bounds(); + let content_layout = layout.children().next().unwrap(); + let content_bounds = content_layout.bounds(); + let translation = + state.translation(self.direction, bounds, content_bounds); + + operation.scrollable( + state, + self.id.as_ref().map(|id| &id.0), + bounds, + translation, + ); operation.container( self.id.as_ref().map(|id| &id.0), + bounds, &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], -- cgit From 09f2887da582ed7d39a56b2c0b37d0b24b6c1e57 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 27 Jul 2023 01:02:47 +0200 Subject: Create `visible_bounds` example --- examples/visible_bounds/Cargo.toml | 10 +++ examples/visible_bounds/src/main.rs | 151 ++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 examples/visible_bounds/Cargo.toml create mode 100644 examples/visible_bounds/src/main.rs diff --git a/examples/visible_bounds/Cargo.toml b/examples/visible_bounds/Cargo.toml new file mode 100644 index 00000000..cfa56dd2 --- /dev/null +++ b/examples/visible_bounds/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "visible_bounds" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } +once_cell = "1" diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs new file mode 100644 index 00000000..bd7ccdc0 --- /dev/null +++ b/examples/visible_bounds/src/main.rs @@ -0,0 +1,151 @@ +use iced::executor; +use iced::mouse; +use iced::subscription::{self, Subscription}; +use iced::theme::{self, Theme}; +use iced::widget::{column, container, scrollable, text, vertical_space}; +use iced::{ + Application, Command, Element, Event, Length, Point, Rectangle, Settings, +}; + +pub fn main() -> iced::Result { + Example::run(Settings::default()) +} + +struct Example { + mouse_position: Option, + outer_bounds: Option, + inner_bounds: Option, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + MouseMoved(Point), + Scrolled(scrollable::Viewport), + OuterBoundsFetched(Option), + InnerBoundsFetched(Option), +} + +impl Application for Example { + type Message = Message; + type Theme = Theme; + type Flags = (); + type Executor = executor::Default; + + fn new(_flags: Self::Flags) -> (Self, Command) { + ( + Self { + mouse_position: None, + outer_bounds: None, + inner_bounds: None, + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Visible bounds - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::MouseMoved(position) => { + self.mouse_position = Some(position); + + Command::none() + } + Message::Scrolled(_) => Command::batch(vec![ + container::visible_bounds(OUTER_CONTAINER.clone()) + .map(Message::OuterBoundsFetched), + container::visible_bounds(INNER_CONTAINER.clone()) + .map(Message::InnerBoundsFetched), + ]), + Message::OuterBoundsFetched(outer_bounds) => { + self.outer_bounds = outer_bounds; + + Command::none() + } + Message::InnerBoundsFetched(inner_bounds) => { + self.inner_bounds = inner_bounds; + + Command::none() + } + } + } + + fn view(&self) -> Element { + let view_bounds = |label, bounds| { + text(format!( + "The {label} container is {}", + match bounds { + Some(bounds) => format!("visible at {:?}", bounds), + None => "not visible".to_string(), + } + )) + }; + + column![ + text(format!( + "Mouse position is {}", + match self.mouse_position { + Some(Point { x, y }) => format!("({x}, {y})"), + None => "unknown".to_string(), + } + )), + view_bounds("outer", self.outer_bounds), + view_bounds("inner", self.inner_bounds), + scrollable( + column![ + text("Scroll me!"), + vertical_space(400), + container(text("I am the outer container!")) + .id(OUTER_CONTAINER.clone()) + .padding(40) + .style(theme::Container::Box), + vertical_space(400), + scrollable( + column![ + text("Scroll me!"), + vertical_space(400), + container(text("I am the inner container!")) + .id(INNER_CONTAINER.clone()) + .padding(40) + .style(theme::Container::Box), + vertical_space(400) + ] + .padding(20) + ) + .on_scroll(Message::Scrolled) + .width(Length::Fill) + .height(300), + ] + .padding(20) + ) + .on_scroll(Message::Scrolled) + .width(Length::Fill) + .height(300), + ] + .spacing(10) + .padding(20) + .into() + } + + fn subscription(&self) -> Subscription { + subscription::events_with(|event, _| match event { + Event::Mouse(mouse::Event::CursorMoved { position }) => { + Some(Message::MouseMoved(position)) + } + _ => None, + }) + } + + fn theme(&self) -> Theme { + Theme::Dark + } +} + +use once_cell::sync::Lazy; + +static OUTER_CONTAINER: Lazy = + Lazy::new(|| container::Id::new("outer")); +static INNER_CONTAINER: Lazy = + Lazy::new(|| container::Id::new("inner")); -- cgit From 8961fcd50179d76fa07edfeedee9edc1a0aa93ec Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 27 Jul 2023 01:21:50 +0200 Subject: Highlight container bounds on hover in `visible_bounds` example --- examples/visible_bounds/src/main.rs | 57 ++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs index bd7ccdc0..8bc645b7 100644 --- a/examples/visible_bounds/src/main.rs +++ b/examples/visible_bounds/src/main.rs @@ -2,9 +2,12 @@ use iced::executor; use iced::mouse; use iced::subscription::{self, Subscription}; use iced::theme::{self, Theme}; -use iced::widget::{column, container, scrollable, text, vertical_space}; +use iced::widget::{ + column, container, horizontal_space, row, scrollable, text, vertical_space, +}; use iced::{ - Application, Command, Element, Event, Length, Point, Rectangle, Settings, + Alignment, Application, Color, Command, Element, Event, Font, Length, + Point, Rectangle, Settings, }; pub fn main() -> iced::Result { @@ -73,26 +76,52 @@ impl Application for Example { } fn view(&self) -> Element { - let view_bounds = |label, bounds| { - text(format!( - "The {label} container is {}", + let data_row = |label, value, color| { + row![ + text(label), + horizontal_space(Length::Fill), + text(value).font(Font::MONOSPACE).size(14).style(color), + ] + .height(40) + .align_items(Alignment::Center) + }; + + let view_bounds = |label, bounds: Option| { + data_row( + label, match bounds { - Some(bounds) => format!("visible at {:?}", bounds), + Some(bounds) => format!("{:?}", bounds), None => "not visible".to_string(), - } - )) + }, + if bounds + .zip(self.mouse_position) + .map(|(bounds, mouse_position)| { + bounds.contains(mouse_position) + }) + .unwrap_or_default() + { + Color { + g: 1.0, + ..Color::BLACK + } + .into() + } else { + theme::Text::Default + }, + ) }; column![ - text(format!( - "Mouse position is {}", + data_row( + "Mouse position", match self.mouse_position { Some(Point { x, y }) => format!("({x}, {y})"), None => "unknown".to_string(), - } - )), - view_bounds("outer", self.outer_bounds), - view_bounds("inner", self.inner_bounds), + }, + theme::Text::Default, + ), + view_bounds("Outer container", self.outer_bounds), + view_bounds("Inner container", self.inner_bounds), scrollable( column![ text("Scroll me!"), -- cgit From 34851c9c394f1200be613c3947acc418ed69f2a7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 27 Jul 2023 01:24:57 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6a989f6..c68ad2c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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) +- `visible_bounds` widget operation for `Container`. [#1971](https://github.com/iced-rs/iced/pull/1971) - 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) -- cgit From cbb5fcc8829e6fbe60f97cad8597c86ffd4f5b1a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 27 Jul 2023 01:29:20 +0200 Subject: Fetch bounds on window resize in `visible_bounds` example --- examples/visible_bounds/src/main.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs index 8bc645b7..8b684514 100644 --- a/examples/visible_bounds/src/main.rs +++ b/examples/visible_bounds/src/main.rs @@ -5,6 +5,7 @@ use iced::theme::{self, Theme}; use iced::widget::{ column, container, horizontal_space, row, scrollable, text, vertical_space, }; +use iced::window; use iced::{ Alignment, Application, Color, Command, Element, Event, Font, Length, Point, Rectangle, Settings, @@ -23,6 +24,7 @@ struct Example { #[derive(Debug, Clone, Copy)] enum Message { MouseMoved(Point), + WindowResized, Scrolled(scrollable::Viewport), OuterBoundsFetched(Option), InnerBoundsFetched(Option), @@ -56,12 +58,14 @@ impl Application for Example { Command::none() } - Message::Scrolled(_) => Command::batch(vec![ - container::visible_bounds(OUTER_CONTAINER.clone()) - .map(Message::OuterBoundsFetched), - container::visible_bounds(INNER_CONTAINER.clone()) - .map(Message::InnerBoundsFetched), - ]), + Message::Scrolled(_) | Message::WindowResized => { + Command::batch(vec![ + container::visible_bounds(OUTER_CONTAINER.clone()) + .map(Message::OuterBoundsFetched), + container::visible_bounds(INNER_CONTAINER.clone()) + .map(Message::InnerBoundsFetched), + ]) + } Message::OuterBoundsFetched(outer_bounds) => { self.outer_bounds = outer_bounds; @@ -163,6 +167,9 @@ impl Application for Example { Event::Mouse(mouse::Event::CursorMoved { position }) => { Some(Message::MouseMoved(position)) } + Event::Window(window::Event::Resized { .. }) => { + Some(Message::WindowResized) + } _ => None, }) } -- cgit From e5a054f2704881145ceeba074668bdb0129f8eed Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 27 Jul 2023 01:42:19 +0200 Subject: Improve name of `verify` workflow step --- .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 3de8a21c..089dd5c9 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -13,7 +13,7 @@ jobs: - name: Check `CHANGELOG.md` has changes run: | ! git diff --exit-code origin/master HEAD -- CHANGELOG.md - - name: Check `CHANGELOG.md` has PR author + - name: Check `CHANGELOG.md` thanks the PR author 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.login }}' -- cgit From a9b95f39dd8f55b00b5f4b2f6690c05b5a921d4d Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Thu, 27 Jul 2023 04:27:19 +0200 Subject: add border radius to public API --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index ce6c513d..174fd890 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -191,7 +191,7 @@ pub use crate::core::event; pub use crate::core::gradient; pub use crate::core::{ color, Alignment, Background, Color, ContentFit, Degrees, Gradient, Length, - Padding, Pixels, Point, Radians, Rectangle, Size, Vector, + Padding, Pixels, Point, Radians, Rectangle, Size, Vector, BorderRadius }; pub use crate::runtime::Command; -- cgit From e4b75ec9c76402dda7c6b6631918da240bb1c7ed Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 27 Jul 2023 16:51:06 +0200 Subject: Update `glyphon` to `0.3` :tada: --- wgpu/Cargo.toml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index a0500f83..3c08b569 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -15,13 +15,14 @@ web-colors = ["iced_graphics/web-colors"] [dependencies] wgpu = "0.16" +glyphon = "0.3" raw-window-handle = "0.5" -log = "0.4" guillotiere = "0.6" futures = "0.3" bitflags = "1.2" once_cell = "1.0" rustc-hash = "1.1" +log = "0.4" [target.'cfg(target_arch = "wasm32")'.dependencies] wgpu = { version = "0.16", features = ["webgl"] } @@ -42,11 +43,6 @@ features = ["derive"] version = "0.8" path = "../graphics" -[dependencies.glyphon] -version = "0.2" -git = "https://github.com/grovesNL/glyphon.git" -rev = "81dedbd04237e181c2118931c5f37d341aeb6837" - [dependencies.glam] version = "0.24" -- cgit From 274fae5aff5dbfce59cec3eaa4893d1ed6b7e459 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 27 Jul 2023 18:50:56 +0200 Subject: Run `cargo fmt` --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 174fd890..0905a31c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,8 +190,8 @@ pub use crate::core::alignment; pub use crate::core::event; pub use crate::core::gradient; pub use crate::core::{ - color, Alignment, Background, Color, ContentFit, Degrees, Gradient, Length, - Padding, Pixels, Point, Radians, Rectangle, Size, Vector, BorderRadius + color, Alignment, Background, BorderRadius, Color, ContentFit, Degrees, + Gradient, Length, Padding, Pixels, Point, Radians, Rectangle, Size, Vector, }; pub use crate::runtime::Command; -- cgit From 74e14e562eef1e837c2cffc58cf23f29652f16c5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 27 Jul 2023 18:51:49 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6a989f6..8836b5de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,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) +- `BorderRadius` not exposed in root crate. [#1972](https://github.com/iced-rs/iced/pull/1972) - Outdated `ROADMAP`. [#1958](https://github.com/iced-rs/iced/pull/1958) Many thanks to... @@ -99,6 +100,7 @@ Many thanks to... - @tarkah - @thunderstorm010 - @wash2 +- @wiiznokes ## [0.9.0] - 2023-04-13 ### Added -- cgit From 126aef88e7647c4690055b4c96aee46ecadcf60e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 28 Jul 2023 19:48:39 +0200 Subject: Bump versions :tada: --- Cargo.toml | 8 ++++---- README.md | 2 +- core/Cargo.toml | 2 +- core/src/lib.rs | 5 +---- core/src/widget.rs | 10 +++++----- futures/Cargo.toml | 4 ++-- futures/src/subscription.rs | 8 ++++---- graphics/Cargo.toml | 4 ++-- renderer/Cargo.toml | 11 +++++++++-- runtime/Cargo.toml | 6 +++--- runtime/src/lib.rs | 4 ++-- runtime/src/user_interface.rs | 2 +- src/application.rs | 18 +++++++++--------- src/lib.rs | 8 ++++---- src/sandbox.rs | 22 +++++++++++----------- style/Cargo.toml | 4 ++-- tiny_skia/Cargo.toml | 9 ++++++++- wgpu/Cargo.toml | 4 ++-- wgpu/src/lib.rs | 9 +++------ widget/Cargo.toml | 11 +++++++++-- widget/src/pane_grid.rs | 2 +- winit/Cargo.toml | 6 +++--- winit/src/conversion.rs | 25 ++++++++++++------------- winit/src/lib.rs | 4 ++-- 24 files changed, 101 insertions(+), 87 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 070cd0ce..05bb883d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced" -version = "0.9.0" +version = "0.10.0" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "A cross-platform GUI library inspired by Elm" @@ -61,11 +61,11 @@ members = [ ] [dependencies] -iced_core = { version = "0.9", path = "core" } -iced_futures = { version = "0.6", path = "futures" } +iced_core = { version = "0.10", path = "core" } +iced_futures = { version = "0.7", path = "futures" } iced_renderer = { version = "0.1", path = "renderer" } iced_widget = { version = "0.1", path = "widget" } -iced_winit = { version = "0.9", path = "winit", features = ["application"] } +iced_winit = { version = "0.10", path = "winit", features = ["application"] } thiserror = "1" [dependencies.image_rs] diff --git a/README.md b/README.md index e013246a..9604aadb 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ __Iced is currently experimental software.__ [Take a look at the roadmap], Add `iced` as a dependency in your `Cargo.toml`: ```toml -iced = "0.9" +iced = "0.10" ``` If your project is using a Rust edition older than 2021, then you will need to diff --git a/core/Cargo.toml b/core/Cargo.toml index 55f2e85f..8bb37309 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_core" -version = "0.9.0" +version = "0.10.0" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "The essential concepts of Iced" diff --git a/core/src/lib.rs b/core/src/lib.rs index 76d775e7..c1c8424b 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,14 +1,11 @@ //! The core library of [Iced]. //! //! This library holds basic types that can be reused and re-exported in -//! different runtime implementations. For instance, both [`iced_native`] and -//! [`iced_web`] are built on top of `iced_core`. +//! different runtime implementations. //! //! ![The foundations of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/foundations.png?raw=true) //! //! [Iced]: https://github.com/iced-rs/iced -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native -//! [`iced_web`]: https://github.com/iced-rs/iced_web #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] diff --git a/core/src/widget.rs b/core/src/widget.rs index 25c1cae8..d6a99208 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -33,12 +33,12 @@ use crate::{Clipboard, Length, Rectangle, Shell}; /// - [`geometry`], a custom widget showcasing how to draw geometry with the /// `Mesh2D` primitive in [`iced_wgpu`]. /// -/// [examples]: https://github.com/iced-rs/iced/tree/0.9/examples -/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.9/examples/bezier_tool -/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.9/examples/custom_widget -/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.9/examples/geometry +/// [examples]: https://github.com/iced-rs/iced/tree/0.10/examples +/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.10/examples/bezier_tool +/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.10/examples/custom_widget +/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.10/examples/geometry /// [`lyon`]: https://github.com/nical/lyon -/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/wgpu +/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.10/wgpu pub trait Widget where Renderer: crate::Renderer, diff --git a/futures/Cargo.toml b/futures/Cargo.toml index f636a304..044827c2 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_futures" -version = "0.6.0" +version = "0.7.0" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "Commands, subscriptions, and runtimes for Iced" @@ -17,7 +17,7 @@ thread-pool = ["futures/thread-pool"] log = "0.4" [dependencies.iced_core] -version = "0.9" +version = "0.10" path = "../core" [dependencies.futures] diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index 0642a924..8f78ce3a 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -128,9 +128,9 @@ impl std::fmt::Debug for Subscription { /// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how /// to listen to time. /// -/// [examples]: https://github.com/iced-rs/iced/tree/0.9/examples -/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.9/examples/download_progress -/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.9/examples/stopwatch +/// [examples]: https://github.com/iced-rs/iced/tree/0.10/examples +/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.10/examples/download_progress +/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.10/examples/stopwatch pub trait Recipe { /// The events that will be produced by a [`Subscription`] with this /// [`Recipe`]. @@ -413,7 +413,7 @@ where /// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket /// connection open. /// -/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.9/examples/websocket +/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.10/examples/websocket pub fn channel( id: I, size: usize, diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 7a9e6aee..ca7bf61a 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_graphics" -version = "0.8.0" +version = "0.9.0" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced" @@ -29,7 +29,7 @@ version = "1.4" features = ["derive"] [dependencies.iced_core] -version = "0.9" +version = "0.10" path = "../core" [dependencies.image] diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index fda2bc7b..89326d73 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -1,7 +1,14 @@ [package] name = "iced_renderer" version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] edition = "2021" +description = "The official renderer for Iced" +license = "MIT" +repository = "https://github.com/iced-rs/iced" +documentation = "https://docs.rs/iced_renderer" +keywords = ["gui", "ui", "graphics", "interface", "widgets"] +categories = ["gui"] [features] wgpu = ["iced_wgpu"] @@ -17,7 +24,7 @@ thiserror = "1" log = "0.4" [dependencies.iced_graphics] -version = "0.8" +version = "0.9" path = "../graphics" [dependencies.iced_tiny_skia] @@ -25,6 +32,6 @@ version = "0.1" path = "../tiny_skia" [dependencies.iced_wgpu] -version = "0.10" +version = "0.11" path = "../wgpu" optional = true diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index a65f07f2..7ee4f6b7 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -3,7 +3,7 @@ name = "iced_runtime" version = "0.1.0" authors = ["Héctor Ramón Jiménez "] edition = "2021" -description = "A renderer-agnostic library for native GUIs" +description = "The renderer-agnostic runtime for Iced" license = "MIT" repository = "https://github.com/iced-rs/iced" @@ -14,10 +14,10 @@ debug = [] thiserror = "1" [dependencies.iced_core] -version = "0.9" +version = "0.10" path = "../core" [dependencies.iced_futures] -version = "0.6" +version = "0.7" path = "../futures" features = ["thread-pool"] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 4bbf9687..59c70ff7 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -23,8 +23,8 @@ //! - Build a new renderer, see the [renderer] module. //! - Build a custom widget, start at the [`Widget`] trait. //! -//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.9/core -//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.9/winit +//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.10/core +//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.10/winit //! [`druid`]: https://github.com/xi-editor/druid //! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle //! [renderer]: crate::renderer diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 8a936b98..5e910eab 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -19,7 +19,7 @@ use crate::overlay; /// The [`integration`] example uses a [`UserInterface`] to integrate Iced in an /// existing graphical application. /// -/// [`integration`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration +/// [`integration`]: https://github.com/iced-rs/iced/tree/0.10/examples/integration #[allow(missing_debug_implementations)] pub struct UserInterface<'a, Message, Renderer> { root: Element<'a, Message, Renderer>, diff --git a/src/application.rs b/src/application.rs index abf58fa3..9518b8c5 100644 --- a/src/application.rs +++ b/src/application.rs @@ -39,15 +39,15 @@ pub use crate::style::application::{Appearance, StyleSheet}; /// to listen to time. /// - [`todos`], a todos tracker inspired by [TodoMVC]. /// -/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.9/examples -/// [`clock`]: https://github.com/iced-rs/iced/tree/0.9/examples/clock -/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.9/examples/download_progress -/// [`events`]: https://github.com/iced-rs/iced/tree/0.9/examples/events -/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.9/examples/game_of_life -/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.9/examples/pokedex -/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.9/examples/solar_system -/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.9/examples/stopwatch -/// [`todos`]: https://github.com/iced-rs/iced/tree/0.9/examples/todos +/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.10/examples +/// [`clock`]: https://github.com/iced-rs/iced/tree/0.10/examples/clock +/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.10/examples/download_progress +/// [`events`]: https://github.com/iced-rs/iced/tree/0.10/examples/events +/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.10/examples/game_of_life +/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.10/examples/pokedex +/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.10/examples/solar_system +/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.10/examples/stopwatch +/// [`todos`]: https://github.com/iced-rs/iced/tree/0.10/examples/todos /// [`Sandbox`]: crate::Sandbox /// [`Canvas`]: crate::widget::Canvas /// [PokéAPI]: https://pokeapi.co/ diff --git a/src/lib.rs b/src/lib.rs index 0905a31c..19e7456e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,13 +24,13 @@ //! [scrollables]: https://gfycat.com/perkybaggybaboon-rust-gui //! [Debug overlay with performance metrics]: https://gfycat.com/incredibledarlingbee //! [Modular ecosystem]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md -//! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/tree/0.9/native +//! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/tree/0.10/runtime //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs -//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.9/wgpu -//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.9/winit +//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.10/wgpu +//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.10/winit //! [`dodrio`]: https://github.com/fitzgen/dodrio //! [web runtime]: https://github.com/iced-rs/iced_web -//! [examples]: https://github.com/iced-rs/iced/tree/0.9/examples +//! [examples]: https://github.com/iced-rs/iced/tree/0.10/examples //! [repository]: https://github.com/iced-rs/iced //! //! # Overview diff --git a/src/sandbox.rs b/src/sandbox.rs index cca327b6..825a0b60 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -34,19 +34,19 @@ use crate::{Application, Command, Element, Error, Settings, Subscription}; /// - [`tour`], a simple UI tour that can run both on native platforms and the /// web! /// -/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.9/examples -/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.9/examples/bezier_tool -/// [`counter`]: https://github.com/iced-rs/iced/tree/0.9/examples/counter -/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.9/examples/custom_widget -/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.9/examples/geometry -/// [`pane_grid`]: https://github.com/iced-rs/iced/tree/0.9/examples/pane_grid -/// [`progress_bar`]: https://github.com/iced-rs/iced/tree/0.9/examples/progress_bar -/// [`styling`]: https://github.com/iced-rs/iced/tree/0.9/examples/styling -/// [`svg`]: https://github.com/iced-rs/iced/tree/0.9/examples/svg -/// [`tour`]: https://github.com/iced-rs/iced/tree/0.9/examples/tour +/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.10/examples +/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.10/examples/bezier_tool +/// [`counter`]: https://github.com/iced-rs/iced/tree/0.10/examples/counter +/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.10/examples/custom_widget +/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.10/examples/geometry +/// [`pane_grid`]: https://github.com/iced-rs/iced/tree/0.10/examples/pane_grid +/// [`progress_bar`]: https://github.com/iced-rs/iced/tree/0.10/examples/progress_bar +/// [`styling`]: https://github.com/iced-rs/iced/tree/0.10/examples/styling +/// [`svg`]: https://github.com/iced-rs/iced/tree/0.10/examples/svg +/// [`tour`]: https://github.com/iced-rs/iced/tree/0.10/examples/tour /// [`Canvas widget`]: crate::widget::Canvas /// [the overview]: index.html#overview -/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/wgpu +/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.10/wgpu /// [`Svg` widget]: crate::widget::Svg /// [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg /// diff --git a/style/Cargo.toml b/style/Cargo.toml index 8af4a9b3..689cf978 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_style" -version = "0.8.0" +version = "0.9.0" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "The default set of styles of Iced" @@ -11,7 +11,7 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [dependencies.iced_core] -version = "0.9" +version = "0.10" path = "../core" features = ["palette"] diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 66ad35fd..9aa63a4f 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -1,7 +1,14 @@ [package] name = "iced_tiny_skia" version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] edition = "2021" +description = "A software renderer for Iced" +license = "MIT" +repository = "https://github.com/iced-rs/iced" +documentation = "https://docs.rs/iced_tiny_skia" +keywords = ["gui", "ui", "graphics", "interface", "widgets"] +categories = ["gui"] [features] image = ["iced_graphics/image"] @@ -19,7 +26,7 @@ kurbo = "0.9" log = "0.4" [dependencies.iced_graphics] -version = "0.8" +version = "0.9" path = "../graphics" [dependencies.twox-hash] diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 3c08b569..f3713eb9 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_wgpu" -version = "0.10.0" +version = "0.11.0" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "A wgpu renderer for Iced" @@ -40,7 +40,7 @@ version = "1.9" features = ["derive"] [dependencies.iced_graphics] -version = "0.8" +version = "0.9" path = "../graphics" [dependencies.glam] diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index deb223ef..d1e4b7af 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -1,25 +1,22 @@ -//! A [`wgpu`] renderer for [`iced_native`]. +//! A [`wgpu`] renderer for [Iced]. //! //! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true) //! -//! For now, it is the default renderer of [Iced] in native platforms. -//! //! [`wgpu`] supports most modern graphics backends: Vulkan, Metal, DX11, and //! DX12 (OpenGL and WebGL are still WIP). Additionally, it will support the //! incoming [WebGPU API]. //! //! Currently, `iced_wgpu` supports the following primitives: -//! - Text, which is rendered using [`wgpu_glyph`]. No shaping at all. +//! - Text, which is rendered using [`glyphon`]. //! - Quads or rectangles, with rounded borders and a solid background color. //! - Clip areas, useful to implement scrollables or hide overflowing content. //! - Images and SVG, loaded from memory or the file system. //! - Meshes of triangles, useful to draw geometry freely. //! //! [Iced]: https://github.com/iced-rs/iced -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [WebGPU API]: https://gpuweb.github.io/gpuweb/ -//! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph +//! [`glyphon`]: https://github.com/grovesNL/glyphon #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 14aae72e..6e66689d 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -1,7 +1,14 @@ [package] name = "iced_widget" -version = "0.1.0" +version = "0.1.1" +authors = ["Héctor Ramón Jiménez "] edition = "2021" +description = "The built-in widgets for Iced" +license = "MIT" +repository = "https://github.com/iced-rs/iced" +documentation = "https://docs.rs/iced_widget" +keywords = ["gui", "ui", "graphics", "interface", "widgets"] +categories = ["gui"] [features] lazy = ["ouroboros"] @@ -24,7 +31,7 @@ version = "0.1" path = "../renderer" [dependencies.iced_style] -version = "0.8" +version = "0.9" path = "../style" [dependencies.ouroboros] diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 0f4ab9eb..d8c98858 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -6,7 +6,7 @@ //! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, //! drag and drop, and hotkey support. //! -//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.9/examples/pane_grid +//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.10/examples/pane_grid mod axis; mod configuration; mod content; diff --git a/winit/Cargo.toml b/winit/Cargo.toml index de7c1c62..67216147 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_winit" -version = "0.9.1" +version = "0.10.0" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "A winit runtime for Iced" @@ -39,11 +39,11 @@ version = "0.1" path = "../runtime" [dependencies.iced_graphics] -version = "0.8" +version = "0.9" path = "../graphics" [dependencies.iced_style] -version = "0.8" +version = "0.9" path = "../style" [dependencies.tracing] diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index dcae7074..b2398e62 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -1,7 +1,7 @@ -//! Convert [`winit`] types into [`iced_native`] types, and viceversa. +//! Convert [`winit`] types into [`iced_runtime`] types, and viceversa. //! //! [`winit`]: https://github.com/rust-windowing/winit -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native +//! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.10/runtime use crate::core::keyboard; use crate::core::mouse; use crate::core::touch; @@ -229,10 +229,9 @@ pub fn mode(mode: Option) -> window::Mode { } } -/// Converts a `MouseCursor` from [`iced_native`] to a [`winit`] cursor icon. +/// Converts a [`mouse::Interaction`] to a [`winit`] cursor icon. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native pub fn mouse_interaction( interaction: mouse::Interaction, ) -> winit::window::CursorIcon { @@ -254,10 +253,10 @@ pub fn mouse_interaction( } } -/// Converts a `MouseButton` from [`winit`] to an [`iced_native`] mouse button. +/// Converts a `MouseButton` from [`winit`] to an [`iced`] mouse button. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native +/// [`iced`]: https://github.com/iced-rs/iced/tree/0.10 pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button { match mouse_button { winit::event::MouseButton::Left => mouse::Button::Left, @@ -267,11 +266,11 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button { } } -/// Converts some `ModifiersState` from [`winit`] to an [`iced_native`] -/// modifiers state. +/// Converts some `ModifiersState` from [`winit`] to an [`iced`] modifiers +/// state. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native +/// [`iced`]: https://github.com/iced-rs/iced/tree/0.10 pub fn modifiers( modifiers: winit::event::ModifiersState, ) -> keyboard::Modifiers { @@ -295,10 +294,10 @@ pub fn cursor_position( Point::new(logical_position.x, logical_position.y) } -/// Converts a `Touch` from [`winit`] to an [`iced_native`] touch event. +/// Converts a `Touch` from [`winit`] to an [`iced`] touch event. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native +/// [`iced`]: https://github.com/iced-rs/iced/tree/0.10 pub fn touch_event( touch: winit::event::Touch, scale_factor: f64, @@ -326,10 +325,10 @@ pub fn touch_event( } } -/// Converts a `VirtualKeyCode` from [`winit`] to an [`iced_native`] key code. +/// Converts a `VirtualKeyCode` from [`winit`] to an [`iced`] key code. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native +/// [`iced`]: https://github.com/iced-rs/iced/tree/0.10 pub fn key_code( virtual_keycode: winit::event::VirtualKeyCode, ) -> keyboard::KeyCode { diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 4776ea2c..1a87ca11 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -2,7 +2,7 @@ //! //! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true) //! -//! `iced_winit` offers some convenient abstractions on top of [`iced_native`] +//! `iced_winit` offers some convenient abstractions on top of [`iced_runtime`] //! to quickstart development when using [`winit`]. //! //! It exposes a renderer-agnostic [`Application`] trait that can be implemented @@ -11,7 +11,7 @@ //! Additionally, a [`conversion`] module is available for users that decide to //! implement a custom event loop. //! -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native +//! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.10/runtime //! [`winit`]: https://github.com/rust-windowing/winit //! [`conversion`]: crate::conversion #![doc( -- cgit From d6c6d07ddaa170d288afd43bb117e188cdafcfa4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 28 Jul 2023 19:49:36 +0200 Subject: Fix `iced_runtime` description --- runtime/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 7ee4f6b7..fac010b3 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -3,7 +3,7 @@ name = "iced_runtime" version = "0.1.0" authors = ["Héctor Ramón Jiménez "] edition = "2021" -description = "The renderer-agnostic runtime for Iced" +description = "A renderer-agnostic runtime for Iced" license = "MIT" repository = "https://github.com/iced-rs/iced" -- cgit From 8c7308d9cee3bb8a32b94024e461f612b1eafe04 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 28 Jul 2023 19:55:45 +0200 Subject: Enable all features in `docs.rs` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 05bb883d..71484b7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,7 @@ optional = true [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] -features = ["image", "svg", "canvas", "qr_code"] +all-features = true [profile.release-opt] inherits = "release" -- cgit From 7c772c35fe1cfddb903aa74dfb5f2a58ec0117d3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 28 Jul 2023 19:59:31 +0200 Subject: Enable all features for `iced_widget` in `docs.rs` --- widget/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 6e66689d..2cba4c5a 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -42,3 +42,7 @@ optional = true version = "0.12" optional = true default-features = false + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +all-features = true -- cgit From 226e9d1cb9af6f42261a6269579d69080c7db091 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 28 Jul 2023 20:02:44 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a338925..c1074065 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.10.0] - 2023-07-28 ### 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) @@ -564,7 +565,8 @@ Many thanks to... ### Added - First release! :tada: -[Unreleased]: https://github.com/iced-rs/iced/compare/0.9.0...HEAD +[Unreleased]: https://github.com/iced-rs/iced/compare/0.10.0...HEAD +[0.10.0]: https://github.com/iced-rs/iced/compare/0.9.0...0.10.0 [0.9.0]: https://github.com/iced-rs/iced/compare/0.8.0...0.9.0 [0.8.0]: https://github.com/iced-rs/iced/compare/0.7.0...0.8.0 [0.7.0]: https://github.com/iced-rs/iced/compare/0.6.0...0.7.0 -- cgit From 16a8a494a46361c5bbb0f902a381a182c59a093e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 29 Jul 2023 19:48:04 +0200 Subject: Fix `Tooltip` overlay position inside `Scrollable` --- widget/src/tooltip.rs | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index ff7f960f..faa3f3e1 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -314,7 +314,7 @@ where &self, renderer: &Renderer, bounds: Size, - _position: Point, + position: Point, ) -> layout::Node { let viewport = Rectangle::with_size(bounds); @@ -331,45 +331,43 @@ where ); let text_bounds = text_layout.bounds(); - let x_center = self.content_bounds.x - + (self.content_bounds.width - text_bounds.width) / 2.0; - let y_center = self.content_bounds.y + let x_center = + position.x + (self.content_bounds.width - text_bounds.width) / 2.0; + let y_center = position.y + (self.content_bounds.height - text_bounds.height) / 2.0; let mut tooltip_bounds = { let offset = match self.position { Position::Top => Vector::new( x_center, - self.content_bounds.y - - text_bounds.height - - self.gap - - self.padding, + position.y - text_bounds.height - self.gap - self.padding, ), Position::Bottom => Vector::new( x_center, - self.content_bounds.y + position.y + self.content_bounds.height + self.gap + self.padding, ), Position::Left => Vector::new( - self.content_bounds.x - - text_bounds.width - - self.gap - - self.padding, + position.x - text_bounds.width - self.gap - self.padding, y_center, ), Position::Right => Vector::new( - self.content_bounds.x + position.x + self.content_bounds.width + self.gap + self.padding, y_center, ), - Position::FollowCursor => Vector::new( - self.cursor_position.x, - self.cursor_position.y - text_bounds.height, - ), + Position::FollowCursor => { + let translation = position - self.content_bounds.position(); + + Vector::new( + self.cursor_position.x, + self.cursor_position.y - text_bounds.height, + ) + translation + } }; Rectangle { -- cgit From 34ca3db1ca371ddaeb1196a2ad20d8945017c607 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 29 Jul 2023 19:50:00 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1074065..9ab7b247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,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) +- `Tooltip` overlay position inside `Scrollable`. [#1978](https://github.com/iced-rs/iced/pull/1978) - `BorderRadius` not exposed in root crate. [#1972](https://github.com/iced-rs/iced/pull/1972) - Outdated `ROADMAP`. [#1958](https://github.com/iced-rs/iced/pull/1958) -- cgit From 50ce65b3b7ad10a8537b751b3890d9dcfaecf846 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 29 Jul 2023 20:08:07 +0200 Subject: Bump version of `iced_widget` :tada: --- widget/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 2cba4c5a..51319d29 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_widget" -version = "0.1.1" +version = "0.1.2" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "The built-in widgets for Iced" -- cgit From 32a95171d2c2a7a7ac8c141277b8c3555dbb3e77 Mon Sep 17 00:00:00 2001 From: Casper Rogild Storm Date: Mon, 31 Jul 2023 22:59:42 +0200 Subject: cycle combobox with keybinds --- widget/src/combo_box.rs | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 93fc92b9..c9397433 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -468,11 +468,13 @@ where if let Event::Keyboard(keyboard::Event::KeyPressed { key_code, + modifiers, .. }) = event { - match key_code { - keyboard::KeyCode::Enter => { + let shift_modifer = modifiers.shift(); + match (key_code, shift_modifer) { + (keyboard::KeyCode::Enter, _) => { if let Some(index) = &menu.hovered_option { if let Some(option) = state.filtered_options.options.get(*index) @@ -483,9 +485,19 @@ where event_status = event::Status::Captured; } - keyboard::KeyCode::Up => { + + (keyboard::KeyCode::Up, _) + | (keyboard::KeyCode::Tab, true) => { if let Some(index) = &mut menu.hovered_option { - *index = index.saturating_sub(1); + if *index == 0 { + *index = state + .filtered_options + .options + .len() + .saturating_sub(1); + } else { + *index = index.saturating_sub(1); + } } else { menu.hovered_option = Some(0); } @@ -511,15 +523,28 @@ where event_status = event::Status::Captured; } - keyboard::KeyCode::Down => { + (keyboard::KeyCode::Down, _) + | (keyboard::KeyCode::Tab, false) + if !modifiers.shift() => + { if let Some(index) = &mut menu.hovered_option { - *index = index.saturating_add(1).min( - state + if *index + == state .filtered_options .options .len() - .saturating_sub(1), - ); + .saturating_sub(1) + { + *index = 0; + } else { + *index = index.saturating_add(1).min( + state + .filtered_options + .options + .len() + .saturating_sub(1), + ); + } } else { menu.hovered_option = Some(0); } -- cgit From e1da5fa63525cf749ec5ebbef42703fb761a0dd1 Mon Sep 17 00:00:00 2001 From: Casper Rogild Storm Date: Mon, 31 Jul 2023 23:07:35 +0200 Subject: Update widget/src/combo_box.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Héctor Ramón --- widget/src/combo_box.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index c9397433..5e36ec57 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -529,7 +529,7 @@ where { if let Some(index) = &mut menu.hovered_option { if *index - == state + >= state .filtered_options .options .len() -- cgit From c7e17391c53c9aa076d76982189a5184681aac0c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 2 Aug 2023 22:05:11 +0200 Subject: Fix `iced_wgpu` freezing on empty layers The `render` method would return when an empty layer is encountered without explicitly dropping the `RenderPass` (necessary because we use `ManuallyDrop`), which would then leak memory and freeze `wgpu` until the surface was recreated. --- wgpu/src/backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 9966a38c..c6a17f2c 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -236,7 +236,7 @@ impl Backend { let bounds = (layer.bounds * scale_factor).snap(); if bounds.width < 1 || bounds.height < 1 { - return; + continue; } if !layer.quads.is_empty() { -- cgit From a7609a723ff0102ef8f606ac2c3bbabc30974db9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 2 Aug 2023 22:08:14 +0200 Subject: Avoid empty overlay layer in `iced_wgpu` --- wgpu/src/backend.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index c6a17f2c..68d1f805 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -87,7 +87,10 @@ impl Backend { let transformation = viewport.projection(); let mut layers = Layer::generate(primitives, viewport); - layers.push(Layer::overlay(overlay_text, viewport)); + + if !overlay_text.is_empty() { + layers.push(Layer::overlay(overlay_text, viewport)); + } self.prepare( device, -- cgit From 4dfa2f0abf9c8219d9f04980e9e5959d1a93b555 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 2 Aug 2023 22:12:15 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ab7b247..c94c5de3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Tooltip` overlay position inside `Scrollable`. [#1978](https://github.com/iced-rs/iced/pull/1978) - `BorderRadius` not exposed in root crate. [#1972](https://github.com/iced-rs/iced/pull/1972) - Outdated `ROADMAP`. [#1958](https://github.com/iced-rs/iced/pull/1958) +- `iced_wgpu` freezing on empty layers. [#1996](https://github.com/iced-rs/iced/pull/1996) Many thanks to... -- cgit From 983764db6a65cad673d35d3112145a8e40e82be0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 2 Aug 2023 22:22:51 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ab7b247..f7e60030 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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) +- Keybinds to cycle `ComboBox` options. [#1991](https://github.com/iced-rs/iced/pull/1991) ### Changed - Updated `wgpu` to `0.16`. [#1807](https://github.com/iced-rs/iced/pull/1807) -- cgit From b9e00590b9cc4ab6c855a1d6eaaa1fae34cdaca8 Mon Sep 17 00:00:00 2001 From: Ryan Scheidter Date: Sat, 29 Jul 2023 14:32:28 -0500 Subject: Update README.md to reflect native->runtime rename --- runtime/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/runtime/README.md b/runtime/README.md index 1b0fa857..35c7eb5e 100644 --- a/runtime/README.md +++ b/runtime/README.md @@ -1,12 +1,12 @@ # `iced_runtime` -[![Documentation](https://docs.rs/iced_native/badge.svg)][documentation] -[![Crates.io](https://img.shields.io/crates/v/iced_native.svg)](https://crates.io/crates/iced_native) -[![License](https://img.shields.io/crates/l/iced_native.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE) +[![Documentation](https://docs.rs/iced_runtime/badge.svg)][documentation] +[![Crates.io](https://img.shields.io/crates/v/iced_runtime.svg)](https://crates.io/crates/iced_runtime) +[![License](https://img.shields.io/crates/l/iced_runtime.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE) [![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd) -`iced_runtime` takes [`iced_core`] and builds a native runtime on top of it. +`iced_runtime` takes [`iced_core`] and builds a runtime on top of it. -[documentation]: https://docs.rs/iced_native +[documentation]: https://docs.rs/iced_runtime [`iced_core`]: ../core [`iced_winit`]: ../winit [`druid`]: https://github.com/xi-editor/druid -- cgit From 5493f94984d8babda692177ff9cd540bdd178000 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 2 Aug 2023 22:25:21 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2ffbed4..e3637149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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) - Keybinds to cycle `ComboBox` options. [#1991](https://github.com/iced-rs/iced/pull/1991) +- Outdated mentions of `iced_native` in `README`. [#1979](https://github.com/iced-rs/iced/pull/1979) ### Changed - Updated `wgpu` to `0.16`. [#1807](https://github.com/iced-rs/iced/pull/1807) @@ -102,6 +103,7 @@ Many thanks to... - @nicoburns - @Redhawk18 - @RGBCube +- @rs017991 - @tarkah - @thunderstorm010 - @wash2 -- cgit From 8a589084e1ecc049c78668b85f34791954e247c9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 2 Aug 2023 22:29:24 +0200 Subject: Bump version of `iced_widget` :tada: --- widget/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 51319d29..128a7c38 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_widget" -version = "0.1.2" +version = "0.1.3" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "The built-in widgets for Iced" -- cgit From 51ba0267a6e4f494197c5718b3c8d780d09f3497 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 2 Aug 2023 22:29:39 +0200 Subject: Bump version of `iced_wgpu` :tada: --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index f3713eb9..69568099 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_wgpu" -version = "0.11.0" +version = "0.11.1" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "A wgpu renderer for Iced" -- cgit From dd6d8875585796252f6b54c950a1c9b143d00904 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 2 Aug 2023 22:44:55 +0200 Subject: Bump version of `iced_runtime` :tada: --- runtime/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index fac010b3..9fbe4be7 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_runtime" -version = "0.1.0" +version = "0.1.1" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "A renderer-agnostic runtime for Iced" -- cgit From 004a1f3848fe279a6222c960f84ab271b792a7a0 Mon Sep 17 00:00:00 2001 From: Malcolm Ramsay Date: Thu, 3 Aug 2023 08:42:46 +0930 Subject: fix: Check cursor in bounds when scrolling image::Viewer Ensure that the cursor is within the bounds of the image::Viewer when performing the scrolling. Fixes #1997 --- widget/src/image/viewer.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 0038f858..c04ddfa6 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -154,7 +154,8 @@ where match event { Event::Mouse(mouse::Event::WheelScrolled { delta }) => { - let Some(cursor_position) = cursor.position() else { + // Ensure the cursor is within the bounds of the widget + let Some(cursor_position) = cursor.position_over(bounds) else { return event::Status::Ignored; }; -- cgit From 085842e7651d1ff7794417fe787727347315c3e5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Aug 2023 20:18:31 +0200 Subject: Remove unnecessary comment in `image::viewer` --- widget/src/image/viewer.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index c04ddfa6..6e095667 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -154,7 +154,6 @@ where match event { Event::Mouse(mouse::Event::WheelScrolled { delta }) => { - // Ensure the cursor is within the bounds of the widget let Some(cursor_position) = cursor.position_over(bounds) else { return event::Status::Ignored; }; -- cgit From 30d5361f6369bb0f619191c38de51561bd4b26cc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Aug 2023 20:19:45 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3637149..8e838f8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `BorderRadius` not exposed in root crate. [#1972](https://github.com/iced-rs/iced/pull/1972) - Outdated `ROADMAP`. [#1958](https://github.com/iced-rs/iced/pull/1958) - `iced_wgpu` freezing on empty layers. [#1996](https://github.com/iced-rs/iced/pull/1996) +- `image::Viewer` reacting to any scroll event. [#1998](https://github.com/iced-rs/iced/pull/1998) Many thanks to... @@ -99,6 +100,7 @@ Many thanks to... - @JonathanLindsey - @kr105 - @marienz +- @malramsay64 - @nicksenger - @nicoburns - @Redhawk18 -- cgit From c81f4676fb39f7ac4a9129be3fbabe7888a88042 Mon Sep 17 00:00:00 2001 From: Casper Rogild Storm Date: Sat, 5 Aug 2023 21:47:02 +0200 Subject: ensure no paste with alt --- widget/src/text_input.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index b899eb67..ef6d31ac 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -842,7 +842,9 @@ where shell.publish(message); } keyboard::KeyCode::V => { - if state.keyboard_modifiers.command() { + if state.keyboard_modifiers.command() + && !state.keyboard_modifiers.alt() + { let content = match state.is_pasting.take() { Some(content) => content, None => { -- cgit From 3940947db7f474ec97fe1030678f21a7f19a5c01 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 15 Aug 2023 07:21:59 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e838f8d..14f0e48a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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) -- Keybinds to cycle `ComboBox` options. [#1991](https://github.com/iced-rs/iced/pull/1991) - Outdated mentions of `iced_native` in `README`. [#1979](https://github.com/iced-rs/iced/pull/1979) ### Changed @@ -77,11 +76,15 @@ 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) -- `Tooltip` overlay position inside `Scrollable`. [#1978](https://github.com/iced-rs/iced/pull/1978) - `BorderRadius` not exposed in root crate. [#1972](https://github.com/iced-rs/iced/pull/1972) - Outdated `ROADMAP`. [#1958](https://github.com/iced-rs/iced/pull/1958) + +### Patched +- Keybinds to cycle `ComboBox` options. [#1991](https://github.com/iced-rs/iced/pull/1991) +- `Tooltip` overlay position inside `Scrollable`. [#1978](https://github.com/iced-rs/iced/pull/1978) - `iced_wgpu` freezing on empty layers. [#1996](https://github.com/iced-rs/iced/pull/1996) - `image::Viewer` reacting to any scroll event. [#1998](https://github.com/iced-rs/iced/pull/1998) +- `TextInput` pasting text when `Alt` key is pressed. [#2006](https://github.com/iced-rs/iced/pull/2006) Many thanks to... -- cgit From f7760618b66c85576e8835207b22df1acf249624 Mon Sep 17 00:00:00 2001 From: Alexandra Reaves Date: Mon, 14 Aug 2023 14:33:26 -0400 Subject: fix dead link to `iced_native` in `README.md` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9604aadb..508413e6 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ __Iced is currently experimental software.__ [Take a look at the roadmap], [scrollables]: https://gfycat.com/perkybaggybaboon-rust-gui [Debug overlay with performance metrics]: https://gfycat.com/incredibledarlingbee [Modular ecosystem]: ECOSYSTEM.md -[renderer-agnostic native runtime]: native/ +[renderer-agnostic native runtime]: runtime/ [`wgpu`]: https://github.com/gfx-rs/wgpu [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia [`iced_wgpu`]: wgpu/ -- cgit From 72f7cf17e318a66743d0947e5a0f55cc71e7b609 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 15 Aug 2023 07:27:46 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14f0e48a..f6a87c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `iced_wgpu` freezing on empty layers. [#1996](https://github.com/iced-rs/iced/pull/1996) - `image::Viewer` reacting to any scroll event. [#1998](https://github.com/iced-rs/iced/pull/1998) - `TextInput` pasting text when `Alt` key is pressed. [#2006](https://github.com/iced-rs/iced/pull/2006) +- Broken link to old `iced_native` crate in `README`. [#2024](https://github.com/iced-rs/iced/pull/2024) Many thanks to... @@ -106,6 +107,7 @@ Many thanks to... - @malramsay64 - @nicksenger - @nicoburns +- @NyxAlexandra - @Redhawk18 - @RGBCube - @rs017991 -- cgit From f5b95629009ecde8c6f6388c8f33ec43d30d17d1 Mon Sep 17 00:00:00 2001 From: "Andrew Wheeler(Genusis)" Date: Tue, 15 Aug 2023 01:47:53 -0400 Subject: Bounds Contains update. (#2017) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * changed the way contains works to exclude <= for point.y and point.x on width and height check to avoid multiple selects * update changelog * Update `CHANGELOG` --------- Co-authored-by: Héctor Ramón Jiménez --- CHANGELOG.md | 2 ++ core/src/rectangle.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6a87c1a..5d9c7349 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `image::Viewer` reacting to any scroll event. [#1998](https://github.com/iced-rs/iced/pull/1998) - `TextInput` pasting text when `Alt` key is pressed. [#2006](https://github.com/iced-rs/iced/pull/2006) - Broken link to old `iced_native` crate in `README`. [#2024](https://github.com/iced-rs/iced/pull/2024) +- `Rectangle::contains` being non-exclusive. [#2017](https://github.com/iced-rs/iced/pull/2017) Many thanks to... @@ -98,6 +99,7 @@ Many thanks to... - @clarkmoody - @Davidster - @Drakulix +- @genusistimelord - @GyulyVGC - @ids1024 - @jhff diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index db56aa18..c1c2eeac 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -74,9 +74,9 @@ impl Rectangle { /// Returns true if the given [`Point`] is contained in the [`Rectangle`]. pub fn contains(&self, point: Point) -> bool { self.x <= point.x - && point.x <= self.x + self.width + && point.x < self.x + self.width && self.y <= point.y - && point.y <= self.y + self.height + && point.y < self.y + self.height } /// Returns true if the current [`Rectangle`] is completely within the given -- cgit From 76ffbbd06e53e2793cba7f7e640279b1f6ef1b12 Mon Sep 17 00:00:00 2001 From: Kxie <56177821+ua-kxie@users.noreply.github.com> Date: Mon, 7 Aug 2023 17:35:26 +0800 Subject: Update arc.rs with improved documentation added units and improved description to field comments of Arc and Ellipse structs. --- graphics/src/geometry/path/arc.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/graphics/src/geometry/path/arc.rs b/graphics/src/geometry/path/arc.rs index 2cdebb66..dd4fcf33 100644 --- a/graphics/src/geometry/path/arc.rs +++ b/graphics/src/geometry/path/arc.rs @@ -8,9 +8,9 @@ pub struct Arc { pub center: Point, /// The radius of the arc. pub radius: f32, - /// The start of the segment's angle, clockwise rotation. + /// The start of the segment's angle in radians, clockwise rotation from positive x-axis. pub start_angle: f32, - /// The end of the segment's angle, clockwise rotation. + /// The end of the segment's angle in radians, clockwise rotation from positive x-axis. pub end_angle: f32, } @@ -19,13 +19,13 @@ pub struct Arc { pub struct Elliptical { /// The center of the arc. pub center: Point, - /// The radii of the arc's ellipse, defining its axes. + /// The radii of the arc's ellipse. The horizontal and vertical half-dimensions of the ellipse will match the x and y values of the radii vector. pub radii: Vector, - /// The rotation of the arc's ellipse. + /// The clockwise rotation of the arc's ellipse. pub rotation: f32, - /// The start of the segment's angle, clockwise rotation. + /// The start of the segment's angle in radians, clockwise rotation from positive x-axis. pub start_angle: f32, - /// The end of the segment's angle, clockwise rotation. + /// The end of the segment's angle in radians, clockwise rotation from positive x-axis. pub end_angle: f32, } -- cgit From 5dbf5adb0af83e3acc9d7c62bb75c0a83f8543bf Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 15 Aug 2023 07:53:32 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d9c7349..e69b26eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `TextInput` pasting text when `Alt` key is pressed. [#2006](https://github.com/iced-rs/iced/pull/2006) - Broken link to old `iced_native` crate in `README`. [#2024](https://github.com/iced-rs/iced/pull/2024) - `Rectangle::contains` being non-exclusive. [#2017](https://github.com/iced-rs/iced/pull/2017) +- Documentation for `Arc` and `arc::Elliptical`. [#2008](https://github.com/iced-rs/iced/pull/2008) Many thanks to... @@ -115,6 +116,7 @@ Many thanks to... - @rs017991 - @tarkah - @thunderstorm010 +- @ua-kxie - @wash2 - @wiiznokes -- cgit From c9c5be7b01fbf4c09368cba278942e4a9d985871 Mon Sep 17 00:00:00 2001 From: Bartłomiej Maryńczak Date: Wed, 16 Aug 2023 20:21:57 +0200 Subject: [docs] Don't use &mut self for view() --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 19e7456e..ff87e245 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,7 +86,7 @@ //! use iced::widget::{button, column, text, Column}; //! //! impl Counter { -//! pub fn view(&mut self) -> Column { +//! pub fn view(&self) -> Column { //! // We use a column: a simple vertical layout //! column![ //! // The increment button. We tell it to produce an -- cgit From e86363837d8e3a6241a90cb5b895034f07106059 Mon Sep 17 00:00:00 2001 From: lufte Date: Fri, 18 Aug 2023 18:46:22 -0300 Subject: Make the style attribute available on Font --- core/src/font.rs | 13 +++++++++++++ tiny_skia/src/text.rs | 11 ++++++++++- wgpu/src/text.rs | 11 ++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/core/src/font.rs b/core/src/font.rs index bb425fd6..7f647847 100644 --- a/core/src/font.rs +++ b/core/src/font.rs @@ -10,6 +10,8 @@ pub struct Font { pub weight: Weight, /// The [`Stretch`] of the [`Font`]. pub stretch: Stretch, + /// The [`Style`] of the [`Font`]. + pub style: Style, /// Whether if the [`Font`] is monospaced or not. pub monospaced: bool, } @@ -20,6 +22,7 @@ impl Font { family: Family::SansSerif, weight: Weight::Normal, stretch: Stretch::Normal, + style: Style::Normal, monospaced: false, }; @@ -100,3 +103,13 @@ pub enum Stretch { ExtraExpanded, UltraExpanded, } + +/// The style of some text. +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Style { + #[default] + Normal, + Italic, + Oblique, +} diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 15f25740..08fde4bf 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -233,6 +233,14 @@ fn to_stretch(stretch: font::Stretch) -> cosmic_text::Stretch { } } +fn to_style(style: font::Style) -> cosmic_text::Style { + match style { + font::Style::Normal => cosmic_text::Style::Normal, + font::Style::Italic => cosmic_text::Style::Italic, + font::Style::Oblique => cosmic_text::Style::Oblique, + } +} + fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping { match shaping { Shaping::Basic => cosmic_text::Shaping::Basic, @@ -411,7 +419,8 @@ impl Cache { cosmic_text::Attrs::new() .family(to_family(key.font.family)) .weight(to_weight(key.font.weight)) - .stretch(to_stretch(key.font.stretch)), + .stretch(to_stretch(key.font.stretch)) + .style(to_style(key.font.style)), to_shaping(key.shaping), ); diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index ef910c39..fb13545d 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -339,6 +339,14 @@ fn to_stretch(stretch: font::Stretch) -> glyphon::Stretch { } } +fn to_style(style: font::Style) -> glyphon::Style { + match style { + font::Style::Normal => glyphon::Style::Normal, + font::Style::Italic => glyphon::Style::Italic, + font::Style::Oblique => glyphon::Style::Oblique, + } +} + fn to_shaping(shaping: Shaping) -> glyphon::Shaping { match shaping { Shaping::Basic => glyphon::Shaping::Basic, @@ -420,7 +428,8 @@ impl Cache { glyphon::Attrs::new() .family(to_family(key.font.family)) .weight(to_weight(key.font.weight)) - .stretch(to_stretch(key.font.stretch)), + .stretch(to_stretch(key.font.stretch)) + .style(to_style(key.font.style)), to_shaping(key.shaping), ); -- cgit From c31a63c1cc7191507beca451bcb391656a13e974 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 21 Aug 2023 15:24:17 +0200 Subject: Update `CONTRIBUTING` guidelines --- CONTRIBUTING.md | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 412cf08f..9c4cacc8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,32 +2,21 @@ 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!). +The core team is busy and does not have time to mentor nor babysit new contributors. If a member of the core team thinks that reviewing and understanding your work will take more time and effort than writing it from scratch by themselves, your contribution will be dismissed. It is your responsibility to communicate and figure out how to help us! -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]! +The general advice for new contributors is to share your ideas with the community. Introduce yourself over our [Discord server] or [start a discussion](https://github.com/iced-rs/iced/discussions) explaining what you have in mind. This is a very important step. It helps to coordinate work, get on the same page, and start building trust. Remember that [Code is the Easy Part] and also [The Hard Parts of Open Source]! -Provided you get in touch first, all kinds of contributions are welcome! Here are a few interesting ideas: - -- New widgets: toggle, table, grid, color picker, video... -- New renderers: `iced_piet` (already in the works!), `iced_skia`, `iced_raqote`, `iced_pathfinder`... -- New shells: `iced_sdl` could be useful for gamedev! -- Better style generation for `iced_web` -- Optimizations for `iced_wgpu`: tiling, incremental rendering... -- Alternative to [`wgpu_glyph`] for proper (shaped), efficient text rendering -- Time travelling debugger built on top of Iced itself -- Testing library -- Cool website to serve on https://iced.rs +Once you have started a channel of communication, you must wait until someone from the core team chimes in. If the core team is busy, this can take a long time (maybe months!). Your idea may need a bunch of iteration, or it may turn into something completely different, or it may be completely discarded! You will have to be patient and humble. Remember that open-source is a gift. Besides directly writing code, there are many other different ways you can contribute. To name a few: - Writing tutorials or blog posts - Improving the documentation - Submitting bug reports and use cases -- Sharing, discussing, researching and exploring new ideas +- Sharing, discussing, researching and exploring new ideas or crates [the ecosystem overview]: ECOSYSTEM.md [the roadmap]: ROADMAP.md [Discord server]: https://discord.gg/3xZJ65GAhd [Code is the Easy Part]: https://youtu.be/DSjbTC-hvqQ?t=1138 [The Hard Parts of Open Source]: https://www.youtube.com/watch?v=o_4EX4dPppA -[`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph -- cgit From f99f0a3644108579566b8eacf47edf653870b1d2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 21 Aug 2023 15:24:41 +0200 Subject: Create `PULL_REQUEST_TEMPLATE` --- .github/PULL_REQUEST_TEMPLATE.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..c4e22460 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,3 @@ +The core team is busy and does not have time to mentor nor babysit new contributors. If a member of the core team thinks that reviewing and understanding your work will take more time and effort than writing it from scratch by themselves, your contribution will be dismissed. It is your responsibility to communicate and figure out how to help us! + +Read the contributing guide for more details: https://github.com/iced-rs/iced/blob/master/CONTRIBUTING.md -- cgit From fb02040b39b5be5125c1ed2e0f14b92479ba3fdb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 21 Aug 2023 15:26:49 +0200 Subject: Clarify the `CONTRIBUTING` guidelines a bit --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- CONTRIBUTING.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c4e22460..84668d7b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,3 @@ -The core team is busy and does not have time to mentor nor babysit new contributors. If a member of the core team thinks that reviewing and understanding your work will take more time and effort than writing it from scratch by themselves, your contribution will be dismissed. It is your responsibility to communicate and figure out how to help us! +The core team is busy and does not have time to mentor nor babysit new contributors. If a member of the core team thinks that reviewing and understanding your work will take more time and effort than writing it from scratch by themselves, your contribution will be dismissed. It is your responsibility to communicate and figure out how to reduce the likelihood of this! Read the contributing guide for more details: https://github.com/iced-rs/iced/blob/master/CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9c4cacc8..a3081b96 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 core team is busy and does not have time to mentor nor babysit new contributors. If a member of the core team thinks that reviewing and understanding your work will take more time and effort than writing it from scratch by themselves, your contribution will be dismissed. It is your responsibility to communicate and figure out how to help us! +The core team is busy and does not have time to mentor nor babysit new contributors. If a member of the core team thinks that reviewing and understanding your work will take more time and effort than writing it from scratch by themselves, your contribution will be dismissed. It is your responsibility to communicate and figure out how to reduce the likelihood of this! The general advice for new contributors is to share your ideas with the community. Introduce yourself over our [Discord server] or [start a discussion](https://github.com/iced-rs/iced/discussions) explaining what you have in mind. This is a very important step. It helps to coordinate work, get on the same page, and start building trust. Remember that [Code is the Easy Part] and also [The Hard Parts of Open Source]! -- cgit From 7e5be8680b997aa10062617763c49f4497d95908 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 21 Aug 2023 15:29:22 +0200 Subject: Replace `guide` with `guidelines` in `PULL_REQUEST_TEMPLATE` --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 84668d7b..224bb8f3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,3 @@ The core team is busy and does not have time to mentor nor babysit new contributors. If a member of the core team thinks that reviewing and understanding your work will take more time and effort than writing it from scratch by themselves, your contribution will be dismissed. It is your responsibility to communicate and figure out how to reduce the likelihood of this! -Read the contributing guide for more details: https://github.com/iced-rs/iced/blob/master/CONTRIBUTING.md +Read the contributing guidelines for more details: https://github.com/iced-rs/iced/blob/master/CONTRIBUTING.md -- cgit From 398a3f08973f39ad7cb67a236b2e6b44e57d453b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 23 Aug 2023 20:50:31 +0200 Subject: Use `Dark` theme in `stopwatch` example --- examples/stopwatch/src/main.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index 9581a3ce..842ba3d4 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -134,4 +134,8 @@ impl Application for Stopwatch { .center_y() .into() } + + fn theme(&self) -> Theme { + Theme::Dark + } } -- cgit From 0ae136b5737253e0e74c93e2491ef25f307b73e9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 23 Aug 2023 21:01:15 +0200 Subject: Update vulnerable `env_logger` dependency in examples --- examples/game_of_life/Cargo.toml | 2 +- examples/multitouch/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml index f0a794fb..6de45db6 100644 --- a/examples/game_of_life/Cargo.toml +++ b/examples/game_of_life/Cargo.toml @@ -10,4 +10,4 @@ iced = { path = "../..", features = ["canvas", "tokio", "debug"] } tokio = { version = "1.0", features = ["sync"] } itertools = "0.9" rustc-hash = "1.1" -env_logger = "0.9" +env_logger = "0.10" diff --git a/examples/multitouch/Cargo.toml b/examples/multitouch/Cargo.toml index f7c8c145..867312f8 100644 --- a/examples/multitouch/Cargo.toml +++ b/examples/multitouch/Cargo.toml @@ -8,5 +8,5 @@ publish = false [dependencies] iced = { path = "../..", features = ["canvas", "tokio", "debug"] } tokio = { version = "1.0", features = ["sync"] } -env_logger = "0.9" +env_logger = "0.10" voronator = "0.2" -- cgit From 8830554e4d68289bf037757e5e3e71a50f334512 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 23 Aug 2023 21:01:44 +0200 Subject: Update vulnerable `async-tungstenite` dependency in `websocket` example --- examples/websocket/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index 03b240c6..12af9658 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -10,7 +10,7 @@ iced = { path = "../..", features = ["tokio", "debug"] } once_cell = "1.15" [dependencies.async-tungstenite] -version = "0.16" +version = "0.23" features = ["tokio-rustls-webpki-roots"] [dependencies.tokio] -- cgit From 96b435488ac911d8f3d7b5e75d1f690bf03a93e6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 26 Aug 2023 01:27:01 +0200 Subject: Add new Discourse forum badge to `README` --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 508413e6..2f9d3db3 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ [![License](https://img.shields.io/crates/l/iced.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE) [![Downloads](https://img.shields.io/crates/d/iced.svg)](https://crates.io/crates/iced) [![Test Status](https://img.shields.io/github/actions/workflow/status/iced-rs/iced/test.yml?branch=master&event=push&label=test)](https://github.com/iced-rs/iced/actions) +[![Discourse](https://img.shields.io/discourse/users?server=https%3A%2F%2Fdiscourse.iced.rs&logo=discourse&labelColor=202225&color=5e7ce2)](https://discourse.iced.rs) [![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd) A cross-platform GUI library for Rust focused on simplicity and type-safety. -- cgit From 36120d5685048f761caf4b6a12a4f3a6007f9363 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 26 Aug 2023 01:31:11 +0200 Subject: Run `cargo fmt` with Rust 1.72 --- tiny_skia/src/geometry.rs | 12 +++++++++--- widget/src/scrollable.rs | 12 ++++++------ widget/src/text_input.rs | 8 ++++++-- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 9bd47556..0fae7364 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -39,7 +39,9 @@ impl Frame { } pub fn fill(&mut self, path: &Path, fill: impl Into) { - let Some(path) = convert_path(path) else { return }; + let Some(path) = convert_path(path) else { + return; + }; let fill = fill.into(); self.primitives @@ -57,7 +59,9 @@ impl Frame { size: Size, fill: impl Into, ) { - let Some(path) = convert_path(&Path::rectangle(top_left, size)) else { return }; + let Some(path) = convert_path(&Path::rectangle(top_left, size)) else { + return; + }; let fill = fill.into(); self.primitives @@ -73,7 +77,9 @@ impl Frame { } pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { - let Some(path) = convert_path(path) else { return }; + let Some(path) = convert_path(path) else { + return; + }; let stroke = stroke.into(); let skia_stroke = into_stroke(&stroke); diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 103e3944..a83ed985 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -593,7 +593,7 @@ pub fn update( match event { touch::Event::FingerPressed { .. } => { let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored + return event::Status::Ignored; }; state.scroll_area_touched_at = Some(cursor_position); @@ -603,7 +603,7 @@ pub fn update( state.scroll_area_touched_at { let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored + return event::Status::Ignored; }; let delta = Vector::new( @@ -648,7 +648,7 @@ pub fn update( | Event::Touch(touch::Event::FingerMoved { .. }) => { if let Some(scrollbar) = scrollbars.y { let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored + return event::Status::Ignored; }; state.scroll_y_to( @@ -678,7 +678,7 @@ pub fn update( Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored + return event::Status::Ignored; }; if let (Some(scroller_grabbed_at), Some(scrollbar)) = @@ -722,7 +722,7 @@ pub fn update( Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored + return event::Status::Ignored; }; if let Some(scrollbar) = scrollbars.x { @@ -753,7 +753,7 @@ pub fn update( Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored + return event::Status::Ignored; }; if let (Some(scroller_grabbed_at), Some(scrollbar)) = diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index ef6d31ac..61fc0055 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -693,7 +693,9 @@ where let state = state(); if let Some(focus) = &mut state.is_focused { - let Some(on_input) = on_input else { return event::Status::Ignored }; + let Some(on_input) = on_input else { + return event::Status::Ignored; + }; if state.is_pasting.is_none() && !state.keyboard_modifiers.command() @@ -716,7 +718,9 @@ where let state = state(); if let Some(focus) = &mut state.is_focused { - let Some(on_input) = on_input else { return event::Status::Ignored }; + let Some(on_input) = on_input else { + return event::Status::Ignored; + }; let modifiers = state.keyboard_modifiers; focus.updated_at = Instant::now(); -- cgit From e0eb19d4cd2002c449286e67abe9a88071cd4909 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 26 Aug 2023 01:32:08 +0200 Subject: Fix consistency of Discourse badge in `README` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f9d3db3..f8165d3c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![License](https://img.shields.io/crates/l/iced.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE) [![Downloads](https://img.shields.io/crates/d/iced.svg)](https://crates.io/crates/iced) [![Test Status](https://img.shields.io/github/actions/workflow/status/iced-rs/iced/test.yml?branch=master&event=push&label=test)](https://github.com/iced-rs/iced/actions) -[![Discourse](https://img.shields.io/discourse/users?server=https%3A%2F%2Fdiscourse.iced.rs&logo=discourse&labelColor=202225&color=5e7ce2)](https://discourse.iced.rs) +[![Discourse](https://img.shields.io/discourse/users?server=https%3A%2F%2Fdiscourse.iced.rs&color=5e7ce2)](https://discourse.iced.rs) [![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd) A cross-platform GUI library for Rust focused on simplicity and type-safety. -- cgit From 8ed06dc356e0296f0b800d4d6b92998bd4444cc6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 26 Aug 2023 01:34:42 +0200 Subject: Fix `clippy` lints for Rust 1.72 --- runtime/src/user_interface.rs | 2 +- src/lib.rs | 1 + widget/src/combo_box.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 5e910eab..e529c004 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -291,7 +291,7 @@ where let event_statuses = events .iter() .cloned() - .zip(overlay_statuses.into_iter()) + .zip(overlay_statuses) .map(|(event, overlay_status)| { if matches!(overlay_status, event::Status::Captured) { return overlay_status; diff --git a/src/lib.rs b/src/lib.rs index ff87e245..36f48ba2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -268,6 +268,7 @@ pub mod touch { pub use crate::core::touch::{Event, Finger}; } +#[allow(hidden_glob_reexports)] pub mod widget { //! Use the built-in widgets or create your own. pub use iced_widget::*; diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 5e36ec57..690ef27c 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -735,7 +735,7 @@ where options .into_iter() - .zip(option_matchers.into_iter()) + .zip(option_matchers) // 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)) { -- cgit From c9bd48704dd9679c033dd0b8588e2744a3df44a0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 26 Aug 2023 19:32:09 +0200 Subject: Replace GitHub discussions with Discourse --- .github/ISSUE_TEMPLATE/config.yml | 10 +++++----- CONTRIBUTING.md | 5 +++-- README.md | 10 +++++----- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 00e4748d..5177386c 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,11 @@ blank_issues_enabled: false contact_links: - name: I have a question - url: https://github.com/iced-rs/iced/discussions/new?category=q-a - about: Open a discussion with a Q&A format. + url: https://discourse.iced.rs/c/learn/6 + about: Ask and learn from others in the Discourse forum. - name: I want to start a discussion - url: https://github.com/iced-rs/iced/discussions/new - about: Open a new discussion if you have any suggestions, ideas, feature requests, or simply want to show off something you've made. + url: https://discourse.iced.rs/c/request-feedback/7 + about: Share your idea and gather feedback in the Discourse forum. - name: I want to chat with other users of the library url: https://discord.com/invite/3xZJ65GAhd - about: Join the Discord Server and get involved with the community! + about: Join the Discord server and get involved with the community! diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3081b96..c01acc4e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Thank you for considering contributing to Iced! Feel free to read [the ecosystem The core team is busy and does not have time to mentor nor babysit new contributors. If a member of the core team thinks that reviewing and understanding your work will take more time and effort than writing it from scratch by themselves, your contribution will be dismissed. It is your responsibility to communicate and figure out how to reduce the likelihood of this! -The general advice for new contributors is to share your ideas with the community. Introduce yourself over our [Discord server] or [start a discussion](https://github.com/iced-rs/iced/discussions) explaining what you have in mind. This is a very important step. It helps to coordinate work, get on the same page, and start building trust. Remember that [Code is the Easy Part] and also [The Hard Parts of Open Source]! +The general advice for new contributors is to share your ideas with the community. You can share your ideas and gather feedback in [our Discourse forum]. This is a very important step. It helps to coordinate work, get on the same page, and start building trust. Remember that [Code is the Easy Part] and also [The Hard Parts of Open Source]! Once you have started a channel of communication, you must wait until someone from the core team chimes in. If the core team is busy, this can take a long time (maybe months!). Your idea may need a bunch of iteration, or it may turn into something completely different, or it may be completely discarded! You will have to be patient and humble. Remember that open-source is a gift. @@ -17,6 +17,7 @@ Besides directly writing code, there are many other different ways you can contr [the ecosystem overview]: ECOSYSTEM.md [the roadmap]: ROADMAP.md -[Discord server]: https://discord.gg/3xZJ65GAhd +[our Discourse fourm]: https://discourse.iced.rs/ +[our Discord server]: https://discord.gg/3xZJ65GAhd [Code is the Easy Part]: https://youtu.be/DSjbTC-hvqQ?t=1138 [The Hard Parts of Open Source]: https://www.youtube.com/watch?v=o_4EX4dPppA diff --git a/README.md b/README.md index f8165d3c..431f14f9 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![License](https://img.shields.io/crates/l/iced.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE) [![Downloads](https://img.shields.io/crates/d/iced.svg)](https://crates.io/crates/iced) [![Test Status](https://img.shields.io/github/actions/workflow/status/iced-rs/iced/test.yml?branch=master&event=push&label=test)](https://github.com/iced-rs/iced/actions) -[![Discourse](https://img.shields.io/discourse/users?server=https%3A%2F%2Fdiscourse.iced.rs&color=5e7ce2)](https://discourse.iced.rs) +[![Discourse](https://img.shields.io/discourse/users?server=https%3A%2F%2Fdiscourse.iced.rs&color=5e7ce2)](https://discourse.iced.rs/) [![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd) A cross-platform GUI library for Rust focused on simplicity and type-safety. @@ -202,8 +202,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 a discussion or come chat to our -[Discord server]. +Feedback is also welcome! You can create a new topic in [our Discourse forum] or +come chat to [our Discord server]. ## Sponsors @@ -216,7 +216,7 @@ The development of Iced is sponsored by the [Cryptowatch] team at [Kraken.com] [The Elm Architecture]: https://guide.elm-lang.org/architecture/ [the current issues]: https://github.com/iced-rs/iced/issues [contributing guidelines]: https://github.com/iced-rs/iced/blob/master/CONTRIBUTING.md -[Discord server]: https://discord.gg/3xZJ65GAhd -[Rust Community Discord]: https://bit.ly/rust-community +[our Discourse forum]: https://discourse.iced.rs/ +[our Discord server]: https://discord.gg/3xZJ65GAhd [Cryptowatch]: https://cryptowat.ch/charts [Kraken.com]: https://kraken.com/ -- cgit From ed3454301e663a7cb7d73cd56b57b188f4d14a2f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 30 Aug 2023 04:31:21 +0200 Subject: Implement explicit text caching in the widget state tree --- core/src/element.rs | 6 +- core/src/layout.rs | 28 +- core/src/layout/flex.rs | 12 +- core/src/renderer.rs | 15 +- core/src/renderer/null.rs | 96 ++++-- core/src/text.rs | 161 ++++++---- core/src/widget.rs | 3 +- core/src/widget/text.rs | 136 +++++--- examples/color_palette/src/main.rs | 6 +- examples/combo_box/src/main.rs | 1 - examples/custom_quad/src/main.rs | 1 + examples/custom_widget/src/main.rs | 1 + examples/game_of_life/src/main.rs | 2 +- examples/geometry/src/main.rs | 1 + examples/integration/src/main.rs | 13 +- examples/loading_spinners/src/circular.rs | 1 + examples/loading_spinners/src/linear.rs | 1 + examples/modal/src/main.rs | 11 +- examples/toast/src/main.rs | 6 +- examples/tour/src/main.rs | 4 +- graphics/Cargo.toml | 11 + graphics/fonts/Iced-Icons.ttf | Bin 0 -> 5108 bytes graphics/src/backend.rs | 69 +--- graphics/src/damage.rs | 26 ++ graphics/src/geometry/text.rs | 6 +- graphics/src/lib.rs | 3 +- graphics/src/primitive.rs | 14 +- graphics/src/renderer.rs | 125 ++++---- graphics/src/text.rs | 113 +++++++ graphics/src/text/cache.rs | 120 +++++++ graphics/src/text/paragraph.rs | 246 +++++++++++++++ renderer/src/compositor.rs | 19 +- renderer/src/lib.rs | 73 ++--- renderer/src/settings.rs | 6 +- runtime/src/user_interface.rs | 17 +- src/settings.rs | 6 +- tiny_skia/fonts/Iced-Icons.ttf | Bin 5108 -> 0 bytes tiny_skia/src/backend.rs | 105 +++--- tiny_skia/src/settings.rs | 6 +- tiny_skia/src/text.rs | 305 ++---------------- tiny_skia/src/window/compositor.rs | 15 +- wgpu/Cargo.toml | 9 - wgpu/fonts/Iced-Icons.ttf | Bin 5108 -> 0 bytes wgpu/src/backend.rs | 75 +---- wgpu/src/layer.rs | 29 +- wgpu/src/layer/text.rs | 19 +- wgpu/src/lib.rs | 2 +- wgpu/src/settings.rs | 6 +- wgpu/src/text.rs | 509 ++++++++---------------------- wgpu/src/window/compositor.rs | 9 +- widget/src/button.rs | 23 +- widget/src/canvas.rs | 1 + widget/src/checkbox.rs | 103 +++--- widget/src/column.rs | 2 + widget/src/combo_box.rs | 167 +++++----- widget/src/container.rs | 38 ++- widget/src/image.rs | 1 + widget/src/image/viewer.rs | 1 + widget/src/lazy.rs | 3 +- widget/src/lazy/component.rs | 3 +- widget/src/lazy/responsive.rs | 17 +- widget/src/mouse_area.rs | 3 +- widget/src/overlay/menu.rs | 54 ++-- widget/src/pane_grid.rs | 13 +- widget/src/pane_grid/content.rs | 13 +- widget/src/pane_grid/title_bar.rs | 18 +- widget/src/pick_list.rs | 198 +++++++----- widget/src/progress_bar.rs | 1 + widget/src/qr_code.rs | 1 + widget/src/radio.rs | 72 +++-- widget/src/row.rs | 2 + widget/src/rule.rs | 1 + widget/src/scrollable.rs | 7 +- widget/src/slider.rs | 1 + widget/src/space.rs | 1 + widget/src/svg.rs | 1 + widget/src/text_input.rs | 322 +++++++++---------- widget/src/toggler.rs | 88 +++--- widget/src/tooltip.rs | 17 +- widget/src/vertical_slider.rs | 1 + 80 files changed, 1913 insertions(+), 1708 deletions(-) create mode 100644 graphics/fonts/Iced-Icons.ttf create mode 100644 graphics/src/text.rs create mode 100644 graphics/src/text/cache.rs create mode 100644 graphics/src/text/paragraph.rs delete mode 100644 tiny_skia/fonts/Iced-Icons.ttf delete mode 100644 wgpu/fonts/Iced-Icons.ttf diff --git a/core/src/element.rs b/core/src/element.rs index d2c6358b..0d23a9e7 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -306,10 +306,11 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.widget.layout(renderer, limits) + self.widget.layout(tree, renderer, limits) } fn operate( @@ -491,10 +492,11 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.element.widget.layout(renderer, limits) + self.element.widget.layout(tree, renderer, limits) } fn operate( diff --git a/core/src/layout.rs b/core/src/layout.rs index 04954fb9..50ccf1f4 100644 --- a/core/src/layout.rs +++ b/core/src/layout.rs @@ -7,7 +7,7 @@ pub mod flex; pub use limits::Limits; pub use node::Node; -use crate::{Point, Rectangle, Vector}; +use crate::{Point, Rectangle, Size, Vector}; /// The bounds of a [`Node`] and its children, using absolute coordinates. #[derive(Debug, Clone, Copy)] @@ -63,3 +63,29 @@ impl<'a> Layout<'a> { }) } } + +/// Produces a [`Node`] with two children nodes one right next to each other. +pub fn next_to_each_other( + limits: &Limits, + spacing: f32, + left: impl FnOnce(&Limits) -> Node, + right: impl FnOnce(&Limits) -> Node, +) -> Node { + let left_node = left(limits); + let left_size = left_node.size(); + + let right_limits = limits.shrink(Size::new(left_size.width + spacing, 0.0)); + + let mut right_node = right(&right_limits); + let right_size = right_node.size(); + + right_node.move_to(Point::new(left_size.width + spacing, 0.0)); + + Node::with_children( + Size::new( + left_size.width + spacing + right_size.width, + left_size.height.max(right_size.height), + ), + vec![left_node, right_node], + ) +} diff --git a/core/src/layout/flex.rs b/core/src/layout/flex.rs index 8b967849..86b1a45b 100644 --- a/core/src/layout/flex.rs +++ b/core/src/layout/flex.rs @@ -19,6 +19,7 @@ use crate::Element; use crate::layout::{Limits, Node}; +use crate::widget; use crate::{Alignment, Padding, Point, Size}; /// The main axis of a flex layout. @@ -66,6 +67,7 @@ pub fn resolve( spacing: f32, align_items: Alignment, items: &[Element<'_, Message, Renderer>], + trees: &[widget::Tree], ) -> Node where Renderer: crate::Renderer, @@ -81,7 +83,7 @@ where let mut nodes: Vec = Vec::with_capacity(items.len()); nodes.resize(items.len(), Node::default()); - for (i, child) in items.iter().enumerate() { + for (i, (child, tree)) in items.iter().zip(trees).enumerate() { let fill_factor = match axis { Axis::Horizontal => child.as_widget().width(), Axis::Vertical => child.as_widget().height(), @@ -94,7 +96,8 @@ where let child_limits = Limits::new(Size::ZERO, Size::new(max_width, max_height)); - let layout = child.as_widget().layout(renderer, &child_limits); + let layout = + child.as_widget().layout(tree, renderer, &child_limits); let size = layout.size(); available -= axis.main(size); @@ -108,7 +111,7 @@ where let remaining = available.max(0.0); - for (i, child) in items.iter().enumerate() { + for (i, (child, tree)) in items.iter().zip(trees).enumerate() { let fill_factor = match axis { Axis::Horizontal => child.as_widget().width(), Axis::Vertical => child.as_widget().height(), @@ -133,7 +136,8 @@ where Size::new(max_width, max_height), ); - let layout = child.as_widget().layout(renderer, &child_limits); + let layout = + child.as_widget().layout(tree, renderer, &child_limits); cross = cross.max(axis.cross(layout.size())); nodes[i] = layout; diff --git a/core/src/renderer.rs b/core/src/renderer.rs index 7c73d2e4..1b327e56 100644 --- a/core/src/renderer.rs +++ b/core/src/renderer.rs @@ -5,26 +5,13 @@ mod null; #[cfg(debug_assertions)] pub use null::Null; -use crate::layout; -use crate::{Background, BorderRadius, Color, Element, Rectangle, Vector}; +use crate::{Background, BorderRadius, Color, Rectangle, Vector}; /// A component that can be used by widgets to draw themselves on a screen. pub trait Renderer: Sized { /// The supported theme of the [`Renderer`]. type Theme; - /// Lays out the elements of a user interface. - /// - /// You should override this if you need to perform any operations before or - /// after layouting. For instance, trimming the measurements cache. - fn layout( - &mut self, - element: &Element<'_, Message, Self>, - limits: &layout::Limits, - ) -> layout::Node { - element.as_widget().layout(self, limits) - } - /// Draws the primitives recorded in the given closure in a new layer. /// /// The layer will clip its contents to the provided `bounds`. diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index 5d49699e..55d58a59 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -1,6 +1,7 @@ +use crate::alignment; use crate::renderer::{self, Renderer}; use crate::text::{self, Text}; -use crate::{Background, Font, Point, Rectangle, Size, Vector}; +use crate::{Background, Color, Font, Pixels, Point, Rectangle, Size, Vector}; use std::borrow::Cow; @@ -41,6 +42,7 @@ impl Renderer for Null { impl text::Renderer for Null { type Font = Font; + type Paragraph = (); const ICON_FONT: Font = Font::DEFAULT; const CHECKMARK_ICON: char = '0'; @@ -50,37 +52,83 @@ impl text::Renderer for Null { Font::default() } - fn default_size(&self) -> f32 { - 16.0 + fn default_size(&self) -> Pixels { + Pixels(16.0) } fn load_font(&mut self, _font: Cow<'static, [u8]>) {} - fn measure( - &self, - _content: &str, - _size: f32, - _line_height: text::LineHeight, - _font: Font, - _bounds: Size, - _shaping: text::Shaping, - ) -> Size { - Size::new(0.0, 20.0) + fn create_paragraph(&self, _text: Text<'_, Self::Font>) -> Self::Paragraph { } - fn hit_test( + fn resize_paragraph( &self, - _contents: &str, - _size: f32, - _line_height: text::LineHeight, - _font: Self::Font, - _bounds: Size, - _shaping: text::Shaping, - _point: Point, - _nearest_only: bool, - ) -> Option { + _paragraph: &mut Self::Paragraph, + _new_bounds: Size, + ) { + } + + fn fill_paragraph( + &mut self, + _paragraph: &Self::Paragraph, + _position: Point, + _color: Color, + ) { + } + + fn fill_text( + &mut self, + _paragraph: Text<'_, Self::Font>, + _position: Point, + _color: Color, + ) { + } +} + +impl text::Paragraph for () { + type Font = Font; + + fn content(&self) -> &str { + "" + } + + fn text_size(&self) -> Pixels { + Pixels(16.0) + } + + fn font(&self) -> Self::Font { + Font::default() + } + + fn line_height(&self) -> text::LineHeight { + text::LineHeight::default() + } + + fn shaping(&self) -> text::Shaping { + text::Shaping::default() + } + + fn horizontal_alignment(&self) -> alignment::Horizontal { + alignment::Horizontal::Left + } + + fn vertical_alignment(&self) -> alignment::Vertical { + alignment::Vertical::Top + } + + fn grapheme_position(&self, _line: usize, _index: usize) -> Option { None } - fn fill_text(&mut self, _text: Text<'_, Self::Font>) {} + fn bounds(&self) -> Size { + Size::ZERO + } + + fn min_bounds(&self) -> Size { + Size::ZERO + } + + fn hit_test(&self, _point: Point) -> Option { + None + } } diff --git a/core/src/text.rs b/core/src/text.rs index fc8aa20e..c59c683a 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -1,6 +1,6 @@ //! Draw and interact with text. use crate::alignment; -use crate::{Color, Pixels, Point, Rectangle, Size}; +use crate::{Color, Pixels, Point, Size}; use std::borrow::Cow; use std::hash::{Hash, Hasher}; @@ -12,17 +12,14 @@ pub struct Text<'a, Font> { pub content: &'a str, /// The bounds of the paragraph. - pub bounds: Rectangle, + pub bounds: Size, /// The size of the [`Text`] in logical pixels. - pub size: f32, + pub size: Pixels, /// The line height of the [`Text`]. pub line_height: LineHeight, - /// The color of the [`Text`]. - pub color: Color, - /// The font of the [`Text`]. pub font: Font, @@ -132,7 +129,10 @@ impl Hit { /// A renderer capable of measuring and drawing [`Text`]. pub trait Renderer: crate::Renderer { /// The font type used. - type Font: Copy; + type Font: Copy + PartialEq; + + /// The [`Paragraph`] of this [`Renderer`]. + type Paragraph: Paragraph + 'static; /// The icon font of the backend. const ICON_FONT: Self::Font; @@ -151,62 +151,107 @@ pub trait Renderer: crate::Renderer { fn default_font(&self) -> Self::Font; /// Returns the default size of [`Text`]. - fn default_size(&self) -> f32; + fn default_size(&self) -> Pixels; - /// Measures the text in the given bounds and returns the minimum boundaries - /// that can fit the contents. - fn measure( + /// Loads a [`Self::Font`] from its bytes. + fn load_font(&mut self, font: Cow<'static, [u8]>); + + /// Creates a new [`Paragraph`] laid out with the given [`Text`]. + fn create_paragraph(&self, text: Text<'_, Self::Font>) -> Self::Paragraph; + + /// Lays out the given [`Paragraph`] with some new boundaries. + fn resize_paragraph( &self, - content: &str, - size: f32, - line_height: LineHeight, - font: Self::Font, - bounds: Size, - shaping: Shaping, - ) -> Size; - - /// Measures the width of the text as if it were laid out in a single line. - fn measure_width( + paragraph: &mut Self::Paragraph, + new_bounds: Size, + ); + + /// Updates a [`Paragraph`] to match the given [`Text`], if needed. + fn update_paragraph( &self, - content: &str, - size: f32, - font: Self::Font, - shaping: Shaping, - ) -> f32 { - let bounds = self.measure( - content, - size, - LineHeight::Absolute(Pixels(size)), - font, - Size::INFINITY, - shaping, - ); - - bounds.width + paragraph: &mut Self::Paragraph, + text: Text<'_, Self::Font>, + ) { + if paragraph.content() != text.content + || paragraph.text_size() != text.size + || paragraph.line_height().to_absolute(text.size) + != text.line_height.to_absolute(text.size) + || paragraph.font() != text.font + || paragraph.shaping() != text.shaping + || paragraph.horizontal_alignment() != text.horizontal_alignment + || paragraph.vertical_alignment() != text.vertical_alignment + { + *paragraph = self.create_paragraph(text); + } else if paragraph.bounds() != text.bounds { + self.resize_paragraph(paragraph, text.bounds); + } } - /// Tests whether the provided point is within the boundaries of text - /// laid out with the given parameters, returning information about - /// the nearest character. - /// - /// If `nearest_only` is true, the hit test does not consider whether the - /// the point is interior to any glyph bounds, returning only the character - /// with the nearest centeroid. - fn hit_test( - &self, - contents: &str, - size: f32, - line_height: LineHeight, - font: Self::Font, - bounds: Size, - shaping: Shaping, - point: Point, - nearest_only: bool, - ) -> Option; + /// Draws the given [`Paragraph`] at the given position and with the given + /// [`Color`]. + fn fill_paragraph( + &mut self, + text: &Self::Paragraph, + position: Point, + color: Color, + ); + + /// Draws the given [`Text`] at the given position and with the given + /// [`Color`]. + fn fill_text( + &mut self, + text: Text<'_, Self::Font>, + position: Point, + color: Color, + ); +} +/// A text paragraph. +pub trait Paragraph: Default { + /// The font of this [`Paragraph`]. + type Font; - /// Loads a [`Self::Font`] from its bytes. - fn load_font(&mut self, font: Cow<'static, [u8]>); + /// Returns the content of the [`Paragraph`]. + fn content(&self) -> &str; - /// Draws the given [`Text`]. - fn fill_text(&mut self, text: Text<'_, Self::Font>); + /// Returns the text size of the [`Paragraph`]. + fn text_size(&self) -> Pixels; + + /// Returns the [`LineHeight`] of the [`Paragraph`]. + fn line_height(&self) -> LineHeight; + + /// Returns the [`Font`] of the [`Paragraph`]. + fn font(&self) -> Self::Font; + + /// Returns the [`Shaping`] strategy of the [`Paragraph`]. + fn shaping(&self) -> Shaping; + + /// Returns the horizontal alignment of the [`Paragraph`]. + fn horizontal_alignment(&self) -> alignment::Horizontal; + + /// Returns the vertical alignment of the [`Paragraph`]. + fn vertical_alignment(&self) -> alignment::Vertical; + + /// Returns the boundaries of the [`Paragraph`]. + fn bounds(&self) -> Size; + + /// Returns the minimum boundaries that can fit the contents of the + /// [`Paragraph`]. + fn min_bounds(&self) -> Size; + + /// Tests whether the provided point is within the boundaries of the + /// [`Paragraph`], returning information about the nearest character. + fn hit_test(&self, point: Point) -> Option; + + /// Returns the distance to the given grapheme index in the [`Paragraph`]. + fn grapheme_position(&self, line: usize, index: usize) -> Option; + + /// Returns the minimum width that can fit the contents of the [`Paragraph`]. + fn min_width(&self) -> f32 { + self.min_bounds().width + } + + /// Returns the minimum height that can fit the contents of the [`Paragraph`]. + fn min_height(&self) -> f32 { + self.min_bounds().height + } } diff --git a/core/src/widget.rs b/core/src/widget.rs index d6a99208..70328ff7 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -55,6 +55,7 @@ where /// user interface. fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node; @@ -62,7 +63,7 @@ where /// Draws the [`Widget`] using the associated `Renderer`. fn draw( &self, - state: &Tree, + tree: &Tree, renderer: &mut Renderer, theme: &Renderer::Theme, style: &renderer::Style, diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 79df2b02..0405537b 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -3,11 +3,12 @@ use crate::alignment; use crate::layout; use crate::mouse; use crate::renderer; -use crate::text; -use crate::widget::Tree; -use crate::{Color, Element, Layout, Length, Pixels, Rectangle, Widget}; +use crate::text::{self, Paragraph as _}; +use crate::widget::tree::{self, Tree}; +use crate::{Color, Element, Layout, Length, Pixels, Point, Rectangle, Widget}; use std::borrow::Cow; +use std::cell::RefCell; pub use text::{LineHeight, Shaping}; @@ -19,7 +20,7 @@ where Renderer::Theme: StyleSheet, { content: Cow<'a, str>, - size: Option, + size: Option, line_height: LineHeight, width: Length, height: Length, @@ -53,7 +54,7 @@ where /// Sets the size of the [`Text`]. pub fn size(mut self, size: impl Into) -> Self { - self.size = Some(size.into().0); + self.size = Some(size.into()); self } @@ -117,11 +118,23 @@ where } } +/// The internal state of a [`Text`] widget. +#[derive(Debug, Default)] +pub struct State(RefCell); + impl<'a, Message, Renderer> Widget for Text<'a, Renderer> where Renderer: text::Renderer, Renderer::Theme: StyleSheet, { + fn tag(&self) -> tree::Tag { + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(State(RefCell::new(Renderer::Paragraph::default()))) + } + fn width(&self) -> Length { self.width } @@ -132,30 +145,29 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); - - let size = self.size.unwrap_or_else(|| renderer.default_size()); - - let bounds = renderer.measure( + layout( + tree.state.downcast_ref::>(), + renderer, + limits, + self.width, + self.height, &self.content, - size, self.line_height, - self.font.unwrap_or_else(|| renderer.default_font()), - limits.max(), + self.size, + self.font, + self.horizontal_alignment, + self.vertical_alignment, self.shaping, - ); - - let size = limits.resolve(bounds); - - layout::Node::new(size) + ) } fn draw( &self, - _state: &Tree, + tree: &Tree, renderer: &mut Renderer, theme: &Renderer::Theme, style: &renderer::Style, @@ -163,22 +175,63 @@ where _cursor_position: mouse::Cursor, _viewport: &Rectangle, ) { + let state = tree.state.downcast_ref::>(); + draw( renderer, style, layout, - &self.content, - self.size, - self.line_height, - self.font, + state, theme.appearance(self.style.clone()), - self.horizontal_alignment, - self.vertical_alignment, - self.shaping, ); } } +/// Produces the [`layout::Node`] of a [`Text`] widget. +pub fn layout( + state: &State, + renderer: &Renderer, + limits: &layout::Limits, + width: Length, + height: Length, + content: &str, + line_height: LineHeight, + size: Option, + font: Option, + horizontal_alignment: alignment::Horizontal, + vertical_alignment: alignment::Vertical, + shaping: Shaping, +) -> layout::Node +where + Renderer: text::Renderer, +{ + let limits = limits.width(width).height(height); + let bounds = limits.max(); + + let size = size.unwrap_or_else(|| renderer.default_size()); + let font = font.unwrap_or_else(|| renderer.default_font()); + + let mut paragraph = state.0.borrow_mut(); + + renderer.update_paragraph( + &mut paragraph, + text::Text { + content, + bounds, + size, + line_height, + font, + shaping, + horizontal_alignment, + vertical_alignment, + }, + ); + + let size = limits.resolve(paragraph.min_bounds()); + + layout::Node::new(size) +} + /// Draws text using the same logic as the [`Text`] widget. /// /// Specifically: @@ -193,44 +246,31 @@ pub fn draw( renderer: &mut Renderer, style: &renderer::Style, layout: Layout<'_>, - content: &str, - size: Option, - line_height: LineHeight, - font: Option, + state: &State, appearance: Appearance, - horizontal_alignment: alignment::Horizontal, - vertical_alignment: alignment::Vertical, - shaping: Shaping, ) where Renderer: text::Renderer, { + let paragraph = state.0.borrow(); let bounds = layout.bounds(); - let x = match horizontal_alignment { + let x = match paragraph.horizontal_alignment() { alignment::Horizontal::Left => bounds.x, alignment::Horizontal::Center => bounds.center_x(), alignment::Horizontal::Right => bounds.x + bounds.width, }; - let y = match vertical_alignment { + let y = match paragraph.vertical_alignment() { alignment::Vertical::Top => bounds.y, alignment::Vertical::Center => bounds.center_y(), alignment::Vertical::Bottom => bounds.y + bounds.height, }; - let size = size.unwrap_or_else(|| renderer.default_size()); - - renderer.fill_text(crate::Text { - content, - size, - line_height, - bounds: Rectangle { x, y, ..bounds }, - color: appearance.color.unwrap_or(style.text_color), - font: font.unwrap_or_else(|| renderer.default_font()), - horizontal_alignment, - vertical_alignment, - shaping, - }); + renderer.fill_paragraph( + ¶graph, + Point::new(x, y), + appearance.color.unwrap_or(style.text_color), + ); } impl<'a, Message, Renderer> From> diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 736a9d53..7dc981d9 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -3,8 +3,8 @@ use iced::mouse; use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path}; use iced::widget::{column, row, text, Slider}; use iced::{ - Color, Element, Length, Point, Rectangle, Renderer, Sandbox, Settings, - Size, Vector, + Color, Element, Length, Pixels, Point, Rectangle, Renderer, Sandbox, + Settings, Size, Vector, }; use palette::{ self, convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue, @@ -168,7 +168,7 @@ impl Theme { let mut text = canvas::Text { horizontal_alignment: alignment::Horizontal::Center, vertical_alignment: alignment::Vertical::Top, - size: 15.0, + size: Pixels(15.0), ..canvas::Text::default() }; diff --git a/examples/combo_box/src/main.rs b/examples/combo_box/src/main.rs index 2e6f95d5..4f347667 100644 --- a/examples/combo_box/src/main.rs +++ b/examples/combo_box/src/main.rs @@ -40,7 +40,6 @@ impl Sandbox for Example { Message::Selected(language) => { self.selected_language = Some(language); self.text = language.hello().to_string(); - self.languages.unfocus(); } Message::OptionHovered(language) => { self.text = language.hello().to_string(); diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs index 4b300116..91401f73 100644 --- a/examples/custom_quad/src/main.rs +++ b/examples/custom_quad/src/main.rs @@ -36,6 +36,7 @@ mod quad { fn layout( &self, + _tree: &widget::Tree, _renderer: &Renderer, _limits: &layout::Limits, ) -> layout::Node { diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index 713bc62d..e0a23541 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -43,6 +43,7 @@ mod circle { fn layout( &self, + _tree: &widget::Tree, _renderer: &Renderer, _limits: &layout::Limits, ) -> layout::Node { diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index e951d734..fa711744 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -591,7 +591,7 @@ mod grid { let text = Text { color: Color::WHITE, - size: 14.0, + size: 14.0.into(), position: Point::new(frame.width(), frame.height()), horizontal_alignment: alignment::Horizontal::Right, vertical_alignment: alignment::Vertical::Bottom, diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 3bc7f46b..a0d505b8 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -26,6 +26,7 @@ mod rainbow { fn layout( &self, + _tree: &widget::Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 342d4c69..e011a411 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -8,7 +8,7 @@ use iced_wgpu::graphics::Viewport; use iced_wgpu::{wgpu, Backend, Renderer, Settings}; use iced_winit::core::mouse; use iced_winit::core::renderer; -use iced_winit::core::{Color, Size}; +use iced_winit::core::{Color, Font, Pixels, Size}; use iced_winit::runtime::program; use iced_winit::runtime::Debug; use iced_winit::style::Theme; @@ -143,12 +143,11 @@ pub fn main() -> Result<(), Box> { // Initialize iced let mut debug = Debug::new(); - let mut renderer = Renderer::new(Backend::new( - &device, - &queue, - Settings::default(), - format, - )); + let mut renderer = Renderer::new( + Backend::new(&device, &queue, Settings::default(), format), + Font::default(), + Pixels(16.0), + ); let mut state = program::State::new( controls, diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index 3898d76e..6bcfd507 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -254,6 +254,7 @@ where fn layout( &self, + _tree: &Tree, _renderer: &iced::Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs index 20fbe9f3..3addd7bb 100644 --- a/examples/loading_spinners/src/linear.rs +++ b/examples/loading_spinners/src/linear.rs @@ -175,6 +175,7 @@ where fn layout( &self, + _tree: &Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index 8a48f830..5d47f02c 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -285,10 +285,13 @@ mod modal { fn layout( &self, + tree: &widget::Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.base.as_widget().layout(renderer, limits) + self.base + .as_widget() + .layout(&tree.children[0], renderer, limits) } fn on_event( @@ -408,7 +411,11 @@ mod modal { .width(Length::Fill) .height(Length::Fill); - let mut child = self.content.as_widget().layout(renderer, &limits); + let mut child = self + .content + .as_widget() + .layout(self.tree, renderer, &limits); + child.align(Alignment::Center, Alignment::Center, limits.max()); let mut node = layout::Node::with_children(self.size, vec![child]); diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 42f6c348..01eea3cc 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -326,10 +326,13 @@ mod toast { fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.content.as_widget().layout(renderer, limits) + self.content + .as_widget() + .layout(&tree.children[0], renderer, limits) } fn tag(&self) -> widget::tree::Tag { @@ -517,6 +520,7 @@ mod toast { 10.0, Alignment::End, self.toasts, + self.state, ) .translate(Vector::new(position.x, position.y)) } diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 13bcd5ff..10de2ae1 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -5,7 +5,7 @@ use iced::widget::{ scrollable, slider, text, text_input, toggler, vertical_space, }; use iced::widget::{Button, Column, Container, Slider}; -use iced::{Color, Element, Font, Length, Renderer, Sandbox, Settings}; +use iced::{Color, Element, Font, Length, Pixels, Renderer, Sandbox, Settings}; pub fn main() -> iced::Result { env_logger::init(); @@ -571,7 +571,7 @@ impl<'a> Step { text_input = text_input.icon(text_input::Icon { font: Font::default(), code_point: '🚀', - size: Some(28.0), + size: Some(Pixels(28.0)), spacing: 10.0, side: text_input::Side::Right, }); diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index ca7bf61a..442eb007 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -23,6 +23,9 @@ log = "0.4" raw-window-handle = "0.5" thiserror = "1.0" bitflags = "1.2" +cosmic-text = "0.9" +rustc-hash = "1.1" +unicode-segmentation = "1.6" [dependencies.bytemuck] version = "1.4" @@ -32,6 +35,14 @@ features = ["derive"] version = "0.10" path = "../core" +[dependencies.twox-hash] +version = "1.6" +default-features = false + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash] +version = "1.6.1" +features = ["std"] + [dependencies.image] version = "0.24" optional = true diff --git a/graphics/fonts/Iced-Icons.ttf b/graphics/fonts/Iced-Icons.ttf new file mode 100644 index 00000000..e3273141 Binary files /dev/null and b/graphics/fonts/Iced-Icons.ttf differ diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 59e95bf8..6774b9ca 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -1,8 +1,8 @@ //! Write a graphics backend. -use iced_core::image; -use iced_core::svg; -use iced_core::text; -use iced_core::{Font, Point, Size}; +use crate::core::image; +use crate::core::svg; +use crate::core::Size; +use crate::text; use std::borrow::Cow; @@ -12,70 +12,15 @@ use std::borrow::Cow; pub trait Backend { /// The custom kind of primitives this [`Backend`] supports. type Primitive; - - /// Trims the measurements cache. - /// - /// This method is currently necessary to properly trim the text cache in - /// `iced_wgpu` and `iced_glow` because of limitations in the text rendering - /// pipeline. It will be removed in the future. - fn trim_measurements(&mut self) {} } /// A graphics backend that supports text rendering. pub trait Text { - /// The icon font of the backend. - const ICON_FONT: Font; - - /// The `char` representing a ✔ icon in the [`ICON_FONT`]. - /// - /// [`ICON_FONT`]: Self::ICON_FONT - const CHECKMARK_ICON: char; - - /// The `char` representing a ▼ icon in the built-in [`ICON_FONT`]. - /// - /// [`ICON_FONT`]: Self::ICON_FONT - const ARROW_DOWN_ICON: char; - - /// Returns the default [`Font`]. - fn default_font(&self) -> Font; - - /// Returns the default size of text. - fn default_size(&self) -> f32; - - /// Measures the text contents with the given size and font, - /// returning the size of a laid out paragraph that fits in the provided - /// bounds. - fn measure( - &self, - contents: &str, - size: f32, - line_height: text::LineHeight, - font: Font, - bounds: Size, - shaping: text::Shaping, - ) -> Size; - - /// Tests whether the provided point is within the boundaries of [`Text`] - /// laid out with the given parameters, returning information about - /// the nearest character. - /// - /// If nearest_only is true, the hit test does not consider whether the - /// the point is interior to any glyph bounds, returning only the character - /// with the nearest centeroid. - fn hit_test( - &self, - contents: &str, - size: f32, - line_height: text::LineHeight, - font: Font, - bounds: Size, - shaping: text::Shaping, - point: Point, - nearest_only: bool, - ) -> Option; - /// Loads a [`Font`] from its bytes. fn load_font(&mut self, font: Cow<'static, [u8]>); + + /// Returns the [`cosmic_text::FontSystem`] of the [`Backend`]. + fn font_system(&self) -> &text::FontSystem; } /// A graphics backend that supports image rendering. diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs index 2f29956e..3276c2d4 100644 --- a/graphics/src/damage.rs +++ b/graphics/src/damage.rs @@ -40,6 +40,32 @@ impl Damage for Primitive { bounds.expand(1.5) } + Self::Paragraph { + paragraph, + position, + .. + } => { + let mut bounds = + Rectangle::new(*position, paragraph.min_bounds); + + bounds.x = match paragraph.horizontal_alignment { + alignment::Horizontal::Left => bounds.x, + alignment::Horizontal::Center => { + bounds.x - bounds.width / 2.0 + } + alignment::Horizontal::Right => bounds.x - bounds.width, + }; + + bounds.y = match paragraph.vertical_alignment { + alignment::Vertical::Top => bounds.y, + alignment::Vertical::Center => { + bounds.y - bounds.height / 2.0 + } + alignment::Vertical::Bottom => bounds.y - bounds.height, + }; + + bounds.expand(1.5) + } Self::Quad { bounds, .. } | Self::Image { bounds, .. } | Self::Svg { bounds, .. } => bounds.expand(1.0), diff --git a/graphics/src/geometry/text.rs b/graphics/src/geometry/text.rs index c584f3cd..0bf7ec97 100644 --- a/graphics/src/geometry/text.rs +++ b/graphics/src/geometry/text.rs @@ -1,6 +1,6 @@ use crate::core::alignment; use crate::core::text::{LineHeight, Shaping}; -use crate::core::{Color, Font, Point}; +use crate::core::{Color, Font, Pixels, Point}; /// A bunch of text that can be drawn to a canvas #[derive(Debug, Clone)] @@ -19,7 +19,7 @@ pub struct Text { /// The color of the text pub color: Color, /// The size of the text - pub size: f32, + pub size: Pixels, /// The line height of the text. pub line_height: LineHeight, /// The font of the text @@ -38,7 +38,7 @@ impl Default for Text { content: String::new(), position: Point::ORIGIN, color: Color::BLACK, - size: 16.0, + size: Pixels(16.0), line_height: LineHeight::Relative(1.2), font: Font::default(), horizontal_alignment: alignment::Horizontal::Left, diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index af374a2f..902eb5b0 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,7 +9,7 @@ )] #![deny( missing_debug_implementations, - missing_docs, + //missing_docs, unsafe_code, unused_results, clippy::extra_unused_lifetimes, @@ -34,6 +34,7 @@ pub mod damage; pub mod gradient; pub mod mesh; pub mod renderer; +pub mod text; #[cfg(feature = "geometry")] pub mod geometry; diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 7592a410..cdc8923e 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -3,7 +3,8 @@ use crate::core::alignment; use crate::core::image; use crate::core::svg; use crate::core::text; -use crate::core::{Background, Color, Font, Rectangle, Vector}; +use crate::core::{Background, Color, Font, Pixels, Point, Rectangle, Vector}; +use crate::text::paragraph; use std::sync::Arc; @@ -19,7 +20,7 @@ pub enum Primitive { /// The color of the text color: Color, /// The size of the text in logical pixels - size: f32, + size: Pixels, /// The line height of the text line_height: text::LineHeight, /// The font of the text @@ -31,6 +32,15 @@ pub enum Primitive { /// The shaping strategy of the text. shaping: text::Shaping, }, + /// A paragraph primitive + Paragraph { + /// The [`Paragraph`]. + paragraph: paragraph::Weak, + /// The position of the [`Paragraph`]. + position: Point, + /// The color of the [`Paragraph`]. + color: Color, + }, /// A quad primitive Quad { /// The bounds of the quad diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index c0cec60a..f93f4a6d 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -1,15 +1,15 @@ //! Create a renderer from a [`Backend`]. use crate::backend::{self, Backend}; -use crate::Primitive; - -use iced_core::image; -use iced_core::layout; -use iced_core::renderer; -use iced_core::svg; -use iced_core::text::{self, Text}; -use iced_core::{ - Background, Color, Element, Font, Point, Rectangle, Size, Vector, +use crate::core; +use crate::core::image; +use crate::core::renderer; +use crate::core::svg; +use crate::core::text::Text; +use crate::core::{ + Background, Color, Font, Pixels, Point, Rectangle, Size, Vector, }; +use crate::text; +use crate::Primitive; use std::borrow::Cow; use std::marker::PhantomData; @@ -18,15 +18,23 @@ use std::marker::PhantomData; #[derive(Debug)] pub struct Renderer { backend: B, + default_font: Font, + default_text_size: Pixels, primitives: Vec>, theme: PhantomData, } impl Renderer { /// Creates a new [`Renderer`] from the given [`Backend`]. - pub fn new(backend: B) -> Self { + pub fn new( + backend: B, + default_font: Font, + default_text_size: Pixels, + ) -> Self { Self { backend, + default_font, + default_text_size, primitives: Vec::new(), theme: PhantomData, } @@ -88,16 +96,6 @@ impl Renderer { impl iced_core::Renderer for Renderer { type Theme = T; - fn layout( - &mut self, - element: &Element<'_, Message, Self>, - limits: &layout::Limits, - ) -> layout::Node { - self.backend.trim_measurements(); - - element.as_widget().layout(self, limits) - } - fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { let current = self.start_layer(); @@ -137,77 +135,66 @@ impl iced_core::Renderer for Renderer { } } -impl text::Renderer for Renderer +impl core::text::Renderer for Renderer where B: Backend + backend::Text, { type Font = Font; + type Paragraph = text::Paragraph; - const ICON_FONT: Font = B::ICON_FONT; - const CHECKMARK_ICON: char = B::CHECKMARK_ICON; - const ARROW_DOWN_ICON: char = B::ARROW_DOWN_ICON; + const ICON_FONT: Font = Font::with_name("Iced-Icons"); + const CHECKMARK_ICON: char = '\u{f00c}'; + const ARROW_DOWN_ICON: char = '\u{e800}'; fn default_font(&self) -> Self::Font { - self.backend().default_font() + self.default_font } - fn default_size(&self) -> f32 { - self.backend().default_size() + fn default_size(&self) -> Pixels { + self.default_text_size } - fn measure( - &self, - content: &str, - size: f32, - line_height: text::LineHeight, - font: Font, - bounds: Size, - shaping: text::Shaping, - ) -> Size { - self.backend().measure( - content, - size, - line_height, - font, - bounds, - shaping, - ) + fn load_font(&mut self, bytes: Cow<'static, [u8]>) { + self.backend.load_font(bytes); + } + + fn create_paragraph(&self, text: Text<'_, Self::Font>) -> text::Paragraph { + text::Paragraph::with_text(text, self.backend.font_system()) } - fn hit_test( + fn resize_paragraph( &self, - content: &str, - size: f32, - line_height: text::LineHeight, - font: Font, - bounds: Size, - shaping: text::Shaping, - point: Point, - nearest_only: bool, - ) -> Option { - self.backend().hit_test( - content, - size, - line_height, - font, - bounds, - shaping, - point, - nearest_only, - ) + paragraph: &mut Self::Paragraph, + new_bounds: Size, + ) { + paragraph.resize(new_bounds, self.backend.font_system()); } - fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - self.backend.load_font(bytes); + fn fill_paragraph( + &mut self, + paragraph: &Self::Paragraph, + position: Point, + color: Color, + ) { + self.primitives.push(Primitive::Paragraph { + paragraph: paragraph.downgrade(), + position, + color, + }); } - fn fill_text(&mut self, text: Text<'_, Self::Font>) { + fn fill_text( + &mut self, + text: Text<'_, Self::Font>, + position: Point, + color: Color, + ) { self.primitives.push(Primitive::Text { content: text.content.to_string(), - bounds: text.bounds, + bounds: Rectangle::new(position, text.bounds), size: text.size, line_height: text.line_height, - color: text.color, + color, font: text.font, horizontal_alignment: text.horizontal_alignment, vertical_alignment: text.vertical_alignment, diff --git a/graphics/src/text.rs b/graphics/src/text.rs new file mode 100644 index 00000000..bbe9d7cb --- /dev/null +++ b/graphics/src/text.rs @@ -0,0 +1,113 @@ +pub mod cache; +pub mod paragraph; + +pub use cache::Cache; +pub use paragraph::Paragraph; + +pub use cosmic_text; + +use crate::core::font::{self, Font}; +use crate::core::text::Shaping; +use crate::core::Size; + +use std::sync::{self, Arc, RwLock}; + +#[allow(missing_debug_implementations)] +pub struct FontSystem(RwLock); + +impl FontSystem { + pub fn new() -> Self { + FontSystem(RwLock::new(cosmic_text::FontSystem::new_with_fonts( + [cosmic_text::fontdb::Source::Binary(Arc::new( + include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), + ))] + .into_iter(), + ))) + } + + pub fn get_mut(&mut self) -> &mut cosmic_text::FontSystem { + self.0.get_mut().expect("Lock font system") + } + + pub fn write(&self) -> sync::RwLockWriteGuard<'_, cosmic_text::FontSystem> { + self.0.write().expect("Write font system") + } +} + +impl Default for FontSystem { + fn default() -> Self { + Self::new() + } +} + +pub fn measure(buffer: &cosmic_text::Buffer) -> Size { + let (width, total_lines) = buffer + .layout_runs() + .fold((0.0, 0usize), |(width, total_lines), run| { + (run.line_w.max(width), total_lines + 1) + }); + + Size::new(width, total_lines as f32 * buffer.metrics().line_height) +} + +pub fn to_attributes(font: Font) -> cosmic_text::Attrs<'static> { + cosmic_text::Attrs::new() + .family(to_family(font.family)) + .weight(to_weight(font.weight)) + .stretch(to_stretch(font.stretch)) + .style(to_style(font.style)) +} + +fn to_family(family: font::Family) -> cosmic_text::Family<'static> { + match family { + font::Family::Name(name) => cosmic_text::Family::Name(name), + font::Family::SansSerif => cosmic_text::Family::SansSerif, + font::Family::Serif => cosmic_text::Family::Serif, + font::Family::Cursive => cosmic_text::Family::Cursive, + font::Family::Fantasy => cosmic_text::Family::Fantasy, + font::Family::Monospace => cosmic_text::Family::Monospace, + } +} + +fn to_weight(weight: font::Weight) -> cosmic_text::Weight { + match weight { + font::Weight::Thin => cosmic_text::Weight::THIN, + font::Weight::ExtraLight => cosmic_text::Weight::EXTRA_LIGHT, + font::Weight::Light => cosmic_text::Weight::LIGHT, + font::Weight::Normal => cosmic_text::Weight::NORMAL, + font::Weight::Medium => cosmic_text::Weight::MEDIUM, + font::Weight::Semibold => cosmic_text::Weight::SEMIBOLD, + font::Weight::Bold => cosmic_text::Weight::BOLD, + font::Weight::ExtraBold => cosmic_text::Weight::EXTRA_BOLD, + font::Weight::Black => cosmic_text::Weight::BLACK, + } +} + +fn to_stretch(stretch: font::Stretch) -> cosmic_text::Stretch { + match stretch { + font::Stretch::UltraCondensed => cosmic_text::Stretch::UltraCondensed, + font::Stretch::ExtraCondensed => cosmic_text::Stretch::ExtraCondensed, + font::Stretch::Condensed => cosmic_text::Stretch::Condensed, + font::Stretch::SemiCondensed => cosmic_text::Stretch::SemiCondensed, + font::Stretch::Normal => cosmic_text::Stretch::Normal, + font::Stretch::SemiExpanded => cosmic_text::Stretch::SemiExpanded, + font::Stretch::Expanded => cosmic_text::Stretch::Expanded, + font::Stretch::ExtraExpanded => cosmic_text::Stretch::ExtraExpanded, + font::Stretch::UltraExpanded => cosmic_text::Stretch::UltraExpanded, + } +} + +fn to_style(style: font::Style) -> cosmic_text::Style { + match style { + font::Style::Normal => cosmic_text::Style::Normal, + font::Style::Italic => cosmic_text::Style::Italic, + font::Style::Oblique => cosmic_text::Style::Oblique, + } +} + +pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping { + match shaping { + Shaping::Basic => cosmic_text::Shaping::Basic, + Shaping::Advanced => cosmic_text::Shaping::Advanced, + } +} diff --git a/graphics/src/text/cache.rs b/graphics/src/text/cache.rs new file mode 100644 index 00000000..8aea6715 --- /dev/null +++ b/graphics/src/text/cache.rs @@ -0,0 +1,120 @@ +use crate::core::{Font, Size}; +use crate::text; + +use rustc_hash::{FxHashMap, FxHashSet}; +use std::collections::hash_map; +use std::hash::{BuildHasher, Hash, Hasher}; + +#[allow(missing_debug_implementations)] +#[derive(Default)] +pub struct Cache { + entries: FxHashMap, + aliases: FxHashMap, + recently_used: FxHashSet, + hasher: HashBuilder, +} + +#[cfg(not(target_arch = "wasm32"))] +type HashBuilder = twox_hash::RandomXxHashBuilder64; + +#[cfg(target_arch = "wasm32")] +type HashBuilder = std::hash::BuildHasherDefault; + +impl Cache { + pub fn new() -> Self { + Self::default() + } + + pub fn get(&self, key: &KeyHash) -> Option<&cosmic_text::Buffer> { + self.entries.get(key) + } + + pub fn allocate( + &mut self, + font_system: &mut cosmic_text::FontSystem, + key: Key<'_>, + ) -> (KeyHash, &mut cosmic_text::Buffer) { + let hash = key.hash(self.hasher.build_hasher()); + + if let Some(hash) = self.aliases.get(&hash) { + let _ = self.recently_used.insert(*hash); + + return (*hash, self.entries.get_mut(hash).unwrap()); + } + + if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { + let metrics = cosmic_text::Metrics::new(key.size, key.line_height); + let mut buffer = cosmic_text::Buffer::new(font_system, metrics); + + buffer.set_size( + font_system, + key.bounds.width, + key.bounds.height.max(key.line_height), + ); + buffer.set_text( + font_system, + key.content, + text::to_attributes(key.font), + text::to_shaping(key.shaping), + ); + + let bounds = text::measure(&buffer); + let _ = entry.insert(buffer); + + for bounds in [ + bounds, + Size { + width: key.bounds.width, + ..bounds + }, + ] { + if key.bounds != bounds { + let _ = self.aliases.insert( + Key { bounds, ..key }.hash(self.hasher.build_hasher()), + hash, + ); + } + } + } + + let _ = self.recently_used.insert(hash); + + (hash, self.entries.get_mut(&hash).unwrap()) + } + + pub fn trim(&mut self) { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.aliases + .retain(|_, value| self.recently_used.contains(value)); + + self.recently_used.clear(); + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Key<'a> { + pub content: &'a str, + pub size: f32, + pub line_height: f32, + pub font: Font, + pub bounds: Size, + pub shaping: text::Shaping, +} + +impl Key<'_> { + fn hash(self, mut hasher: H) -> KeyHash { + self.content.hash(&mut hasher); + self.size.to_bits().hash(&mut hasher); + self.line_height.to_bits().hash(&mut hasher); + self.font.hash(&mut hasher); + self.bounds.width.to_bits().hash(&mut hasher); + self.bounds.height.to_bits().hash(&mut hasher); + self.shaping.hash(&mut hasher); + + hasher.finish() + } +} + +pub type KeyHash = u64; diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs new file mode 100644 index 00000000..7b70376a --- /dev/null +++ b/graphics/src/text/paragraph.rs @@ -0,0 +1,246 @@ +use crate::core; +use crate::core::alignment; +use crate::core::text::{Hit, LineHeight, Shaping, Text}; +use crate::core::{Font, Pixels, Point, Size}; +use crate::text::{self, FontSystem}; + +use std::fmt; +use std::sync::{self, Arc}; + +#[derive(Clone, PartialEq, Default)] +pub struct Paragraph(Arc); + +struct Internal { + buffer: cosmic_text::Buffer, + content: String, // TODO: Reuse from `buffer` (?) + font: Font, + shaping: Shaping, + horizontal_alignment: alignment::Horizontal, + vertical_alignment: alignment::Vertical, + bounds: Size, + min_bounds: Size, +} + +impl Paragraph { + pub fn new() -> Self { + Self::default() + } + + pub fn with_text(text: Text<'_, Font>, font_system: &FontSystem) -> Self { + let mut font_system = font_system.write(); + + let mut buffer = cosmic_text::Buffer::new( + &mut font_system, + cosmic_text::Metrics::new( + text.size.into(), + text.line_height.to_absolute(text.size).into(), + ), + ); + + buffer.set_size( + &mut font_system, + text.bounds.width, + text.bounds.height, + ); + + buffer.set_text( + &mut font_system, + text.content, + text::to_attributes(text.font), + text::to_shaping(text.shaping), + ); + + let min_bounds = text::measure(&buffer); + + Self(Arc::new(Internal { + buffer, + content: text.content.to_owned(), + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + shaping: text.shaping, + bounds: text.bounds, + min_bounds, + })) + } + + pub fn buffer(&self) -> &cosmic_text::Buffer { + &self.0.buffer + } + + pub fn downgrade(&self) -> Weak { + Weak { + raw: Arc::downgrade(&self.0), + min_bounds: self.0.min_bounds, + horizontal_alignment: self.0.horizontal_alignment, + vertical_alignment: self.0.vertical_alignment, + } + } + + pub fn resize(&mut self, new_bounds: Size, font_system: &FontSystem) { + if let Some(internal) = Arc::get_mut(&mut self.0) { + // If there is no strong reference holding on to the paragraph, we + // resize the buffer in-place + internal.buffer.set_size( + &mut font_system.write(), + new_bounds.width, + new_bounds.height, + ); + + internal.bounds = new_bounds; + internal.min_bounds = text::measure(&internal.buffer); + } else { + let metrics = self.0.buffer.metrics(); + + // If there is a strong reference somewhere, we recompute the buffer + // from scratch + *self = Self::with_text( + Text { + content: &self.0.content, + bounds: self.0.bounds, + size: Pixels(metrics.font_size), + line_height: LineHeight::Absolute(Pixels( + metrics.line_height, + )), + font: self.0.font, + horizontal_alignment: self.0.horizontal_alignment, + vertical_alignment: self.0.vertical_alignment, + shaping: self.0.shaping, + }, + font_system, + ); + } + } +} + +impl core::text::Paragraph for Paragraph { + type Font = Font; + + fn content(&self) -> &str { + &self.0.content + } + + fn text_size(&self) -> Pixels { + Pixels(self.0.buffer.metrics().font_size) + } + + fn line_height(&self) -> LineHeight { + LineHeight::Absolute(Pixels(self.0.buffer.metrics().line_height)) + } + + fn font(&self) -> Font { + self.0.font + } + + fn shaping(&self) -> Shaping { + self.0.shaping + } + + fn horizontal_alignment(&self) -> alignment::Horizontal { + self.0.horizontal_alignment + } + + fn vertical_alignment(&self) -> alignment::Vertical { + self.0.vertical_alignment + } + + fn bounds(&self) -> Size { + self.0.bounds + } + + fn min_bounds(&self) -> Size { + self.0.min_bounds + } + + fn hit_test(&self, point: Point) -> Option { + let cursor = self.0.buffer.hit(point.x, point.y)?; + + Some(Hit::CharOffset(cursor.index)) + } + + fn grapheme_position(&self, line: usize, index: usize) -> Option { + let run = self.0.buffer.layout_runs().nth(line)?; + + // TODO: Index represents a grapheme, not a glyph + let glyph = run.glyphs.get(index).or_else(|| run.glyphs.last())?; + + let advance_last = if index == run.glyphs.len() { + glyph.w + } else { + 0.0 + }; + + Some(Point::new( + glyph.x + glyph.x_offset * glyph.font_size + advance_last, + glyph.y - glyph.y_offset * glyph.font_size, + )) + } +} + +impl PartialEq for Internal { + fn eq(&self, other: &Self) -> bool { + self.content == other.content + && self.font == other.font + && self.shaping == other.shaping + && self.horizontal_alignment == other.horizontal_alignment + && self.vertical_alignment == other.vertical_alignment + && self.bounds == other.bounds + && self.min_bounds == other.min_bounds + && self.buffer.metrics() == other.buffer.metrics() + } +} + +impl Default for Internal { + fn default() -> Self { + Self { + buffer: cosmic_text::Buffer::new_empty(cosmic_text::Metrics { + font_size: 1.0, + line_height: 1.0, + }), + content: String::new(), + font: Font::default(), + shaping: Shaping::default(), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + bounds: Size::ZERO, + min_bounds: Size::ZERO, + } + } +} + +impl fmt::Debug for Paragraph { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Paragraph") + .field("content", &self.0.content) + .field("font", &self.0.font) + .field("shaping", &self.0.shaping) + .field("horizontal_alignment", &self.0.horizontal_alignment) + .field("vertical_alignment", &self.0.vertical_alignment) + .field("bounds", &self.0.bounds) + .field("min_bounds", &self.0.min_bounds) + .finish() + } +} + +#[derive(Debug, Clone)] +pub struct Weak { + raw: sync::Weak, + pub min_bounds: Size, + pub horizontal_alignment: alignment::Horizontal, + pub vertical_alignment: alignment::Vertical, +} + +impl Weak { + pub fn upgrade(&self) -> Option { + self.raw.upgrade().map(Paragraph) + } +} + +impl PartialEq for Weak { + fn eq(&self, other: &Self) -> bool { + match (self.raw.upgrade(), other.raw.upgrade()) { + (Some(p1), Some(p2)) => p1 == p2, + _ => false, + } + } +} diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs index 8b17a4b0..d1500089 100644 --- a/renderer/src/compositor.rs +++ b/renderer/src/compositor.rs @@ -224,16 +224,15 @@ impl Candidate { match self { Self::TinySkia => { let (compositor, backend) = - iced_tiny_skia::window::compositor::new( - iced_tiny_skia::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - }, - ); + iced_tiny_skia::window::compositor::new(); Ok(( Compositor::TinySkia(compositor), - Renderer::TinySkia(iced_tiny_skia::Renderer::new(backend)), + Renderer::TinySkia(iced_tiny_skia::Renderer::new( + backend, + settings.default_font, + settings.default_text_size, + )), )) } #[cfg(feature = "wgpu")] @@ -250,7 +249,11 @@ impl Candidate { Ok(( Compositor::Wgpu(compositor), - Renderer::Wgpu(iced_wgpu::Renderer::new(backend)), + Renderer::Wgpu(iced_wgpu::Renderer::new( + backend, + settings.default_font, + settings.default_text_size, + )), )) } #[cfg(not(feature = "wgpu"))] diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 7d1a02c2..2b282a0b 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -16,7 +16,10 @@ pub use geometry::Geometry; use crate::core::renderer; use crate::core::text::{self, Text}; -use crate::core::{Background, Font, Point, Rectangle, Size, Vector}; +use crate::core::{ + Background, Color, Font, Pixels, Point, Rectangle, Size, Vector, +}; +use crate::graphics::text::Paragraph; use crate::graphics::Mesh; use std::borrow::Cow; @@ -142,6 +145,7 @@ impl core::Renderer for Renderer { impl text::Renderer for Renderer { type Font = Font; + type Paragraph = Paragraph; const ICON_FONT: Font = iced_tiny_skia::Renderer::::ICON_FONT; const CHECKMARK_ICON: char = iced_tiny_skia::Renderer::::CHECKMARK_ICON; @@ -152,59 +156,50 @@ impl text::Renderer for Renderer { delegate!(self, renderer, renderer.default_font()) } - fn default_size(&self) -> f32 { + fn default_size(&self) -> Pixels { delegate!(self, renderer, renderer.default_size()) } - fn measure( - &self, - content: &str, - size: f32, - line_height: text::LineHeight, - font: Font, - bounds: Size, - shaping: text::Shaping, - ) -> Size { - delegate!( - self, - renderer, - renderer.measure(content, size, line_height, font, bounds, shaping) - ) + fn create_paragraph(&self, text: Text<'_, Self::Font>) -> Self::Paragraph { + delegate!(self, renderer, renderer.create_paragraph(text)) } - fn hit_test( + fn resize_paragraph( &self, - content: &str, - size: f32, - line_height: text::LineHeight, - font: Font, - bounds: Size, - shaping: text::Shaping, - point: Point, - nearest_only: bool, - ) -> Option { + paragraph: &mut Self::Paragraph, + new_bounds: Size, + ) { delegate!( self, renderer, - renderer.hit_test( - content, - size, - line_height, - font, - bounds, - shaping, - point, - nearest_only - ) - ) + renderer.resize_paragraph(paragraph, new_bounds) + ); } fn load_font(&mut self, bytes: Cow<'static, [u8]>) { delegate!(self, renderer, renderer.load_font(bytes)); } - fn fill_text(&mut self, text: Text<'_, Self::Font>) { - delegate!(self, renderer, renderer.fill_text(text)); + fn fill_paragraph( + &mut self, + text: &Self::Paragraph, + position: Point, + color: Color, + ) { + delegate!( + self, + renderer, + renderer.fill_paragraph(text, position, color) + ); + } + + fn fill_text( + &mut self, + text: Text<'_, Self::Font>, + position: Point, + color: Color, + ) { + delegate!(self, renderer, renderer.fill_text(text, position, color)); } } diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs index 2e51f339..08f2099e 100644 --- a/renderer/src/settings.rs +++ b/renderer/src/settings.rs @@ -1,4 +1,4 @@ -use crate::core::Font; +use crate::core::{Font, Pixels}; use crate::graphics::Antialiasing; /// The settings of a [`Backend`]. @@ -12,7 +12,7 @@ pub struct Settings { /// The default size of text. /// /// By default, it will be set to `16.0`. - pub default_text_size: f32, + pub default_text_size: Pixels, /// The antialiasing strategy that will be used for triangle primitives. /// @@ -24,7 +24,7 @@ impl Default for Settings { fn default() -> Settings { Settings { default_font: Font::default(), - default_text_size: 16.0, + default_text_size: Pixels(16.0), antialiasing: None, } } diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index e529c004..a730102c 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -95,8 +95,11 @@ where let Cache { mut state } = cache; state.diff(root.as_widget()); - let base = - renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)); + let base = root.as_widget().layout( + &state, + renderer, + &layout::Limits::new(Size::ZERO, bounds), + ); UserInterface { root, @@ -226,8 +229,9 @@ where if shell.is_layout_invalid() { let _ = ManuallyDrop::into_inner(manual_overlay); - self.base = renderer.layout( - &self.root, + self.base = self.root.as_widget().layout( + &self.state, + renderer, &layout::Limits::new(Size::ZERO, self.bounds), ); @@ -325,8 +329,9 @@ where } shell.revalidate_layout(|| { - self.base = renderer.layout( - &self.root, + self.base = self.root.as_widget().layout( + &self.state, + renderer, &layout::Limits::new(Size::ZERO, self.bounds), ); diff --git a/src/settings.rs b/src/settings.rs index 0dd46584..794f89fd 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,6 +1,6 @@ //! Configure your application. use crate::window; -use crate::Font; +use crate::{Font, Pixels}; /// The settings of an application. #[derive(Debug, Clone)] @@ -29,7 +29,7 @@ pub struct Settings { /// The text size that will be used by default. /// /// The default value is `16.0`. - pub default_text_size: f32, + pub default_text_size: Pixels, /// If set to true, the renderer will try to perform antialiasing for some /// primitives. @@ -80,7 +80,7 @@ where window: Default::default(), flags: Default::default(), default_font: Default::default(), - default_text_size: 16.0, + default_text_size: Pixels(16.0), antialiasing: false, exit_on_close_request: true, } diff --git a/tiny_skia/fonts/Iced-Icons.ttf b/tiny_skia/fonts/Iced-Icons.ttf deleted file mode 100644 index e3273141..00000000 Binary files a/tiny_skia/fonts/Iced-Icons.ttf and /dev/null differ diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index a8add70b..ef587bac 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,16 +1,12 @@ -use crate::core::text; -use crate::core::Gradient; -use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; +use crate::core::{Background, Color, Gradient, Rectangle, Vector}; use crate::graphics::backend; +use crate::graphics::text; use crate::graphics::{Damage, Viewport}; use crate::primitive::{self, Primitive}; -use crate::Settings; use std::borrow::Cow; pub struct Backend { - default_font: Font, - default_text_size: f32, text_pipeline: crate::text::Pipeline, #[cfg(feature = "image")] @@ -21,10 +17,8 @@ pub struct Backend { } impl Backend { - pub fn new(settings: Settings) -> Self { + pub fn new() -> Self { Self { - default_font: settings.default_font, - default_text_size: settings.default_text_size, text_pipeline: crate::text::Pipeline::new(), #[cfg(feature = "image")] @@ -364,6 +358,32 @@ impl Backend { } } } + Primitive::Paragraph { + paragraph, + position, + color, + } => { + let physical_bounds = + (Rectangle::new(*position, paragraph.min_bounds) + + translation) + * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + self.text_pipeline.draw_paragraph( + paragraph, + *position + translation, + *color, + scale_factor, + pixels, + clip_mask, + ); + } Primitive::Text { content, bounds, @@ -599,6 +619,12 @@ impl Backend { } } +impl Default for Backend { + fn default() -> Self { + Self::new() + } +} + fn into_color(color: Color) -> tiny_skia::Color { tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) .expect("Convert color from iced to tiny_skia") @@ -779,58 +805,8 @@ impl iced_graphics::Backend for Backend { } impl backend::Text for Backend { - const ICON_FONT: Font = Font::with_name("Iced-Icons"); - const CHECKMARK_ICON: char = '\u{f00c}'; - const ARROW_DOWN_ICON: char = '\u{e800}'; - - fn default_font(&self) -> Font { - self.default_font - } - - fn default_size(&self) -> f32 { - self.default_text_size - } - - fn measure( - &self, - contents: &str, - size: f32, - line_height: text::LineHeight, - font: Font, - bounds: Size, - shaping: text::Shaping, - ) -> Size { - self.text_pipeline.measure( - contents, - size, - line_height, - font, - bounds, - shaping, - ) - } - - fn hit_test( - &self, - contents: &str, - size: f32, - line_height: text::LineHeight, - font: Font, - bounds: Size, - shaping: text::Shaping, - point: Point, - nearest_only: bool, - ) -> Option { - self.text_pipeline.hit_test( - contents, - size, - line_height, - font, - bounds, - shaping, - point, - nearest_only, - ) + fn font_system(&self) -> &text::FontSystem { + self.text_pipeline.font_system() } fn load_font(&mut self, font: Cow<'static, [u8]>) { @@ -840,7 +816,10 @@ impl backend::Text for Backend { #[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, handle: &crate::core::image::Handle) -> Size { + fn dimensions( + &self, + handle: &crate::core::image::Handle, + ) -> crate::core::Size { self.raster_pipeline.dimensions(handle) } } @@ -850,7 +829,7 @@ impl backend::Svg for Backend { fn viewport_dimensions( &self, handle: &crate::core::svg::Handle, - ) -> Size { + ) -> crate::core::Size { self.vector_pipeline.viewport_dimensions(handle) } } diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs index abffbfe6..ec27b218 100644 --- a/tiny_skia/src/settings.rs +++ b/tiny_skia/src/settings.rs @@ -1,4 +1,4 @@ -use crate::core::Font; +use crate::core::{Font, Pixels}; /// The settings of a [`Backend`]. /// @@ -11,14 +11,14 @@ pub struct Settings { /// The default size of text. /// /// By default, it will be set to `16.0`. - pub default_text_size: f32, + pub default_text_size: Pixels, } impl Default for Settings { fn default() -> Settings { Settings { default_font: Font::default(), - default_text_size: 16.0, + default_text_size: Pixels(16.0), } } } diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 08fde4bf..e4c5ad9b 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -1,18 +1,19 @@ use crate::core::alignment; -use crate::core::font::{self, Font}; -use crate::core::text::{Hit, LineHeight, Shaping}; -use crate::core::{Color, Pixels, Point, Rectangle, Size}; +use crate::core::text::{LineHeight, Shaping}; +use crate::core::{Color, Font, Pixels, Point, Rectangle}; +use crate::graphics::text::cache::{self, Cache}; +use crate::graphics::text::paragraph; +use crate::graphics::text::FontSystem; use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; use std::cell::RefCell; use std::collections::hash_map; -use std::hash::{BuildHasher, Hash, Hasher}; use std::sync::Arc; #[allow(missing_debug_implementations)] pub struct Pipeline { - font_system: RefCell, + font_system: FontSystem, glyph_cache: GlyphCache, cache: RefCell, } @@ -20,17 +21,16 @@ pub struct Pipeline { impl Pipeline { pub fn new() -> Self { Pipeline { - font_system: RefCell::new(cosmic_text::FontSystem::new_with_fonts( - [cosmic_text::fontdb::Source::Binary(Arc::new( - include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), - ))] - .into_iter(), - )), + font_system: FontSystem::new(), glyph_cache: GlyphCache::new(), cache: RefCell::new(Cache::new()), } } + pub fn font_system(&self) -> &FontSystem { + &self.font_system + } + pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { self.font_system.get_mut().db_mut().load_font_source( cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())), @@ -39,12 +39,23 @@ impl Pipeline { self.cache = RefCell::new(Cache::new()); } + pub fn draw_paragraph( + &mut self, + _paragraph: ¶graph::Weak, + _position: Point, + _color: Color, + _scale_factor: f32, + _pixels: &mut tiny_skia::PixmapMut<'_>, + _clip_mask: Option<&tiny_skia::Mask>, + ) { + } + pub fn draw( &mut self, content: &str, bounds: Rectangle, color: Color, - size: f32, + size: Pixels, line_height: LineHeight, font: Font, horizontal_alignment: alignment::Horizontal, @@ -54,22 +65,22 @@ impl Pipeline { pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::Mask>, ) { - let line_height = f32::from(line_height.to_absolute(Pixels(size))); + let line_height = f32::from(line_height.to_absolute(size)); let font_system = self.font_system.get_mut(); - let key = Key { + let key = cache::Key { bounds: bounds.size(), content, font, - size, + size: size.into(), line_height, shaping, }; - let (_, entry) = self.cache.get_mut().allocate(font_system, key); + let (_, buffer) = self.cache.get_mut().allocate(font_system, key); - let max_width = entry.bounds.width * scale_factor; - let total_height = entry.bounds.height * scale_factor; + let max_width = bounds.width * scale_factor; + let total_height = bounds.height * scale_factor; let bounds = bounds * scale_factor; @@ -87,7 +98,7 @@ impl Pipeline { let mut swash = cosmic_text::SwashCache::new(); - for run in entry.buffer.layout_runs() { + for run in buffer.layout_runs() { for glyph in run.glyphs { let physical_glyph = glyph.physical((x, y), scale_factor); @@ -122,130 +133,6 @@ impl Pipeline { self.cache.get_mut().trim(); self.glyph_cache.trim(); } - - pub fn measure( - &self, - content: &str, - size: f32, - line_height: LineHeight, - font: Font, - bounds: Size, - shaping: Shaping, - ) -> Size { - let mut measurement_cache = self.cache.borrow_mut(); - - let line_height = f32::from(line_height.to_absolute(Pixels(size))); - - let (_, entry) = measurement_cache.allocate( - &mut self.font_system.borrow_mut(), - Key { - content, - size, - line_height, - font, - bounds, - shaping, - }, - ); - - entry.bounds - } - - pub fn hit_test( - &self, - content: &str, - size: f32, - line_height: LineHeight, - font: Font, - bounds: Size, - shaping: Shaping, - point: Point, - _nearest_only: bool, - ) -> Option { - let mut measurement_cache = self.cache.borrow_mut(); - - let line_height = f32::from(line_height.to_absolute(Pixels(size))); - - let (_, entry) = measurement_cache.allocate( - &mut self.font_system.borrow_mut(), - Key { - content, - size, - line_height, - font, - bounds, - shaping, - }, - ); - - let cursor = entry.buffer.hit(point.x, point.y)?; - - Some(Hit::CharOffset(cursor.index)) - } -} - -fn measure(buffer: &cosmic_text::Buffer) -> Size { - let (width, total_lines) = buffer - .layout_runs() - .fold((0.0, 0usize), |(width, total_lines), run| { - (run.line_w.max(width), total_lines + 1) - }); - - Size::new(width, total_lines as f32 * buffer.metrics().line_height) -} - -fn to_family(family: font::Family) -> cosmic_text::Family<'static> { - match family { - font::Family::Name(name) => cosmic_text::Family::Name(name), - font::Family::SansSerif => cosmic_text::Family::SansSerif, - font::Family::Serif => cosmic_text::Family::Serif, - font::Family::Cursive => cosmic_text::Family::Cursive, - font::Family::Fantasy => cosmic_text::Family::Fantasy, - font::Family::Monospace => cosmic_text::Family::Monospace, - } -} - -fn to_weight(weight: font::Weight) -> cosmic_text::Weight { - match weight { - font::Weight::Thin => cosmic_text::Weight::THIN, - font::Weight::ExtraLight => cosmic_text::Weight::EXTRA_LIGHT, - font::Weight::Light => cosmic_text::Weight::LIGHT, - font::Weight::Normal => cosmic_text::Weight::NORMAL, - font::Weight::Medium => cosmic_text::Weight::MEDIUM, - font::Weight::Semibold => cosmic_text::Weight::SEMIBOLD, - font::Weight::Bold => cosmic_text::Weight::BOLD, - font::Weight::ExtraBold => cosmic_text::Weight::EXTRA_BOLD, - font::Weight::Black => cosmic_text::Weight::BLACK, - } -} - -fn to_stretch(stretch: font::Stretch) -> cosmic_text::Stretch { - match stretch { - font::Stretch::UltraCondensed => cosmic_text::Stretch::UltraCondensed, - font::Stretch::ExtraCondensed => cosmic_text::Stretch::ExtraCondensed, - font::Stretch::Condensed => cosmic_text::Stretch::Condensed, - font::Stretch::SemiCondensed => cosmic_text::Stretch::SemiCondensed, - font::Stretch::Normal => cosmic_text::Stretch::Normal, - font::Stretch::SemiExpanded => cosmic_text::Stretch::SemiExpanded, - font::Stretch::Expanded => cosmic_text::Stretch::Expanded, - font::Stretch::ExtraExpanded => cosmic_text::Stretch::ExtraExpanded, - font::Stretch::UltraExpanded => cosmic_text::Stretch::UltraExpanded, - } -} - -fn to_style(style: font::Style) -> cosmic_text::Style { - match style { - font::Style::Normal => cosmic_text::Style::Normal, - font::Style::Italic => cosmic_text::Style::Italic, - font::Style::Oblique => cosmic_text::Style::Oblique, - } -} - -fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping { - match shaping { - Shaping::Basic => cosmic_text::Shaping::Basic, - Shaping::Advanced => cosmic_text::Shaping::Advanced, - } } #[derive(Debug, Clone, Default)] @@ -358,135 +245,3 @@ impl GlyphCache { } } } - -struct Cache { - entries: FxHashMap, - measurements: FxHashMap, - recently_used: FxHashSet, - hasher: HashBuilder, - trim_count: usize, -} - -struct Entry { - buffer: cosmic_text::Buffer, - bounds: Size, -} - -#[cfg(not(target_arch = "wasm32"))] -type HashBuilder = twox_hash::RandomXxHashBuilder64; - -#[cfg(target_arch = "wasm32")] -type HashBuilder = std::hash::BuildHasherDefault; - -impl Cache { - const TRIM_INTERVAL: usize = 300; - - fn new() -> Self { - Self { - entries: FxHashMap::default(), - measurements: FxHashMap::default(), - recently_used: FxHashSet::default(), - hasher: HashBuilder::default(), - trim_count: 0, - } - } - - fn allocate( - &mut self, - font_system: &mut cosmic_text::FontSystem, - key: Key<'_>, - ) -> (KeyHash, &mut Entry) { - let hash = key.hash(self.hasher.build_hasher()); - - if let Some(hash) = self.measurements.get(&hash) { - let _ = self.recently_used.insert(*hash); - - return (*hash, self.entries.get_mut(hash).unwrap()); - } - - if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { - let metrics = cosmic_text::Metrics::new(key.size, key.size * 1.2); - let mut buffer = cosmic_text::Buffer::new(font_system, metrics); - - buffer.set_size( - font_system, - key.bounds.width, - key.bounds.height.max(key.size * 1.2), - ); - buffer.set_text( - font_system, - key.content, - cosmic_text::Attrs::new() - .family(to_family(key.font.family)) - .weight(to_weight(key.font.weight)) - .stretch(to_stretch(key.font.stretch)) - .style(to_style(key.font.style)), - to_shaping(key.shaping), - ); - - let bounds = measure(&buffer); - - let _ = entry.insert(Entry { buffer, bounds }); - - for bounds in [ - bounds, - Size { - width: key.bounds.width, - ..bounds - }, - ] { - if key.bounds != bounds { - let _ = self.measurements.insert( - Key { bounds, ..key }.hash(self.hasher.build_hasher()), - hash, - ); - } - } - } - - let _ = self.recently_used.insert(hash); - - (hash, self.entries.get_mut(&hash).unwrap()) - } - - fn trim(&mut self) { - if self.trim_count > Self::TRIM_INTERVAL { - self.entries - .retain(|key, _| self.recently_used.contains(key)); - self.measurements - .retain(|_, value| self.recently_used.contains(value)); - - self.recently_used.clear(); - - self.trim_count = 0; - } else { - self.trim_count += 1; - } - } -} - -#[derive(Debug, Clone, Copy)] -struct Key<'a> { - content: &'a str, - size: f32, - line_height: f32, - font: Font, - bounds: Size, - shaping: Shaping, -} - -impl Key<'_> { - fn hash(self, mut hasher: H) -> KeyHash { - self.content.hash(&mut hasher); - self.size.to_bits().hash(&mut hasher); - self.line_height.to_bits().hash(&mut hasher); - self.font.hash(&mut hasher); - self.bounds.width.to_bits().hash(&mut hasher); - self.bounds.height.to_bits().hash(&mut hasher); - self.shaping.hash(&mut hasher); - - hasher.finish() - } -} - -type KeyHash = u64; diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 775cf9e5..a996fffc 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -28,9 +28,16 @@ impl crate::graphics::Compositor for Compositor { settings: Self::Settings, _compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error> { - let (compositor, backend) = new(settings); + let (compositor, backend) = new(); - Ok((compositor, Renderer::new(backend))) + Ok(( + compositor, + Renderer::new( + backend, + settings.default_font, + settings.default_text_size, + ), + )) } fn create_surface( @@ -113,12 +120,12 @@ impl crate::graphics::Compositor for Compositor { } } -pub fn new(settings: Settings) -> (Compositor, Backend) { +pub fn new() -> (Compositor, Backend) { ( Compositor { _theme: PhantomData, }, - Backend::new(settings), + Backend::new(), ) } diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 69568099..09740f54 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -21,20 +21,11 @@ guillotiere = "0.6" futures = "0.3" bitflags = "1.2" once_cell = "1.0" -rustc-hash = "1.1" log = "0.4" [target.'cfg(target_arch = "wasm32")'.dependencies] wgpu = { version = "0.16", features = ["webgl"] } -[dependencies.twox-hash] -version = "1.6" -default-features = false - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash] -version = "1.6.1" -features = ["std"] - [dependencies.bytemuck] version = "1.9" features = ["derive"] diff --git a/wgpu/fonts/Iced-Icons.ttf b/wgpu/fonts/Iced-Icons.ttf deleted file mode 100644 index e3273141..00000000 Binary files a/wgpu/fonts/Iced-Icons.ttf and /dev/null differ diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 68d1f805..65c63f19 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,5 +1,5 @@ -use crate::core; -use crate::core::{Color, Font, Point, Size}; +use crate::core::{Color, Size}; +use crate::graphics; use crate::graphics::backend; use crate::graphics::color; use crate::graphics::{Transformation, Viewport}; @@ -29,9 +29,6 @@ pub struct Backend { #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, - - default_font: Font, - default_text_size: f32, } impl Backend { @@ -57,9 +54,6 @@ impl Backend { #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, - - default_font: settings.default_font, - default_text_size: settings.default_text_size, } } @@ -313,65 +307,11 @@ impl Backend { impl crate::graphics::Backend for Backend { type Primitive = primitive::Custom; - - fn trim_measurements(&mut self) { - self.text_pipeline.trim_measurements(); - } } impl backend::Text for Backend { - const ICON_FONT: Font = Font::with_name("Iced-Icons"); - const CHECKMARK_ICON: char = '\u{f00c}'; - const ARROW_DOWN_ICON: char = '\u{e800}'; - - fn default_font(&self) -> Font { - self.default_font - } - - fn default_size(&self) -> f32 { - self.default_text_size - } - - fn measure( - &self, - contents: &str, - size: f32, - line_height: core::text::LineHeight, - font: Font, - bounds: Size, - shaping: core::text::Shaping, - ) -> Size { - self.text_pipeline.measure( - contents, - size, - line_height, - font, - bounds, - shaping, - ) - } - - fn hit_test( - &self, - contents: &str, - size: f32, - line_height: core::text::LineHeight, - font: Font, - bounds: Size, - shaping: core::text::Shaping, - point: Point, - nearest_only: bool, - ) -> Option { - self.text_pipeline.hit_test( - contents, - size, - line_height, - font, - bounds, - shaping, - point, - nearest_only, - ) + fn font_system(&self) -> &graphics::text::FontSystem { + self.text_pipeline.font_system() } fn load_font(&mut self, font: Cow<'static, [u8]>) { @@ -381,14 +321,17 @@ impl backend::Text for Backend { #[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, handle: &core::image::Handle) -> Size { + fn dimensions(&self, handle: &crate::core::image::Handle) -> Size { self.image_pipeline.dimensions(handle) } } #[cfg(feature = "svg")] impl backend::Svg for Backend { - fn viewport_dimensions(&self, handle: &core::svg::Handle) -> Size { + fn viewport_dimensions( + &self, + handle: &crate::core::svg::Handle, + ) -> Size { self.image_pipeline.viewport_dimensions(handle) } } diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index b8f32db1..7a5a0f7c 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -10,7 +10,7 @@ pub use text::Text; use crate::core; use crate::core::alignment; -use crate::core::{Color, Font, Point, Rectangle, Size, Vector}; +use crate::core::{Color, Font, Pixels, Point, Rectangle, Size, Vector}; use crate::graphics; use crate::graphics::color; use crate::graphics::Viewport; @@ -56,14 +56,14 @@ impl<'a> Layer<'a> { Layer::new(Rectangle::with_size(viewport.logical_size())); for (i, line) in lines.iter().enumerate() { - let text = Text { + let text = text::Cached { content: line.as_ref(), bounds: Rectangle::new( Point::new(11.0, 11.0 + 25.0 * i as f32), Size::INFINITY, ), color: Color::new(0.9, 0.9, 0.9, 1.0), - size: 20.0, + size: Pixels(20.0), line_height: core::text::LineHeight::default(), font: Font::MONOSPACE, horizontal_alignment: alignment::Horizontal::Left, @@ -71,13 +71,13 @@ impl<'a> Layer<'a> { shaping: core::text::Shaping::Basic, }; - overlay.text.push(text); + overlay.text.push(Text::Cached(text.clone())); - overlay.text.push(Text { + overlay.text.push(Text::Cached(text::Cached { bounds: text.bounds + Vector::new(-1.0, -1.0), color: Color::BLACK, ..text - }); + })); } overlay @@ -113,6 +113,19 @@ impl<'a> Layer<'a> { current_layer: usize, ) { match primitive { + Primitive::Paragraph { + paragraph, + position, + color, + } => { + let layer = &mut layers[current_layer]; + + layer.text.push(Text::Managed { + paragraph: paragraph.clone(), + position: *position + translation, + color: *color, + }); + } Primitive::Text { content, bounds, @@ -126,7 +139,7 @@ impl<'a> Layer<'a> { } => { let layer = &mut layers[current_layer]; - layer.text.push(Text { + layer.text.push(Text::Cached(text::Cached { content, bounds: *bounds + translation, size: *size, @@ -136,7 +149,7 @@ impl<'a> Layer<'a> { horizontal_alignment: *horizontal_alignment, vertical_alignment: *vertical_alignment, shaping: *shaping, - }); + })); } Primitive::Quad { bounds, diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs index ba1bdca8..b61615d6 100644 --- a/wgpu/src/layer/text.rs +++ b/wgpu/src/layer/text.rs @@ -1,10 +1,21 @@ use crate::core::alignment; use crate::core::text; -use crate::core::{Color, Font, Rectangle}; +use crate::core::{Color, Font, Pixels, Point, Rectangle}; +use crate::graphics::text::paragraph; /// A paragraph of text. -#[derive(Debug, Clone, Copy)] -pub struct Text<'a> { +#[derive(Debug, Clone)] +pub enum Text<'a> { + Managed { + paragraph: paragraph::Weak, + position: Point, + color: Color, + }, + Cached(Cached<'a>), +} + +#[derive(Debug, Clone)] +pub struct Cached<'a> { /// The content of the [`Text`]. pub content: &'a str, @@ -15,7 +26,7 @@ pub struct Text<'a> { pub color: Color, /// The size of the [`Text`] in logical pixels. - pub size: f32, + pub size: Pixels, /// The line height of the [`Text`]. pub line_height: text::LineHeight, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index d1e4b7af..cd457072 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -22,7 +22,7 @@ )] #![deny( missing_debug_implementations, - missing_docs, + //missing_docs, unsafe_code, unused_results, clippy::extra_unused_lifetimes, diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 266a2c87..c9338fec 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,5 +1,5 @@ //! Configure a renderer. -use crate::core::Font; +use crate::core::{Font, Pixels}; use crate::graphics::Antialiasing; /// The settings of a [`Backend`]. @@ -21,7 +21,7 @@ pub struct Settings { /// The default size of text. /// /// By default, it will be set to `16.0`. - pub default_text_size: f32, + pub default_text_size: Pixels, /// The antialiasing strategy that will be used for triangle primitives. /// @@ -59,7 +59,7 @@ impl Default for Settings { present_mode: wgpu::PresentMode::AutoVsync, internal_backend: wgpu::Backends::all(), default_font: Font::default(), - default_text_size: 16.0, + default_text_size: Pixels(16.0), antialiasing: None, } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index fb13545d..da2062fe 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,20 +1,17 @@ use crate::core::alignment; -use crate::core::font::{self, Font}; -use crate::core::text::{Hit, LineHeight, Shaping}; -use crate::core::{Pixels, Point, Rectangle, Size}; +use crate::core::{Rectangle, Size}; use crate::graphics::color; +use crate::graphics::text::cache::{self, Cache}; +use crate::graphics::text::{FontSystem, Paragraph}; use crate::layer::Text; -use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; use std::cell::RefCell; -use std::collections::hash_map; -use std::hash::{BuildHasher, Hash, Hasher}; use std::sync::Arc; #[allow(missing_debug_implementations)] pub struct Pipeline { - font_system: RefCell, + font_system: FontSystem, renderers: Vec, atlas: glyphon::TextAtlas, prepare_layer: usize, @@ -28,12 +25,7 @@ impl Pipeline { format: wgpu::TextureFormat, ) -> Self { Pipeline { - font_system: RefCell::new(glyphon::FontSystem::new_with_fonts( - [glyphon::fontdb::Source::Binary(Arc::new( - include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), - ))] - .into_iter(), - )), + font_system: FontSystem::new(), renderers: Vec::new(), atlas: glyphon::TextAtlas::with_color_mode( device, @@ -50,6 +42,10 @@ impl Pipeline { } } + pub fn font_system(&self) -> &FontSystem { + &self.font_system + } + pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { let _ = self.font_system.get_mut().db_mut().load_font_source( glyphon::fontdb::Source::Binary(Arc::new(bytes.into_owned())), @@ -80,97 +76,137 @@ impl Pipeline { let renderer = &mut self.renderers[self.prepare_layer]; let cache = self.cache.get_mut(); - if self.prepare_layer == 0 { - cache.trim(Purpose::Drawing); + enum Allocation { + Paragraph(Paragraph), + Cache(cache::KeyHash), } - let keys: Vec<_> = sections + let allocations: Vec<_> = sections .iter() - .map(|section| { - let (key, _) = cache.allocate( - font_system, - Key { - content: section.content, - size: section.size, - line_height: f32::from( - section - .line_height - .to_absolute(Pixels(section.size)), - ), - font: section.font, - bounds: Size { - width: section.bounds.width, - height: section.bounds.height, + .map(|section| match section { + Text::Managed { paragraph, .. } => { + paragraph.upgrade().map(Allocation::Paragraph) + } + Text::Cached(text) => { + let (key, _) = cache.allocate( + font_system, + cache::Key { + content: text.content, + size: text.size.into(), + line_height: f32::from( + text.line_height.to_absolute(text.size), + ), + font: text.font, + bounds: Size { + width: bounds.width, + height: bounds.height, + }, + shaping: text.shaping, }, - shaping: section.shaping, - }, - Purpose::Drawing, - ); + ); - key + Some(Allocation::Cache(key)) + } }) .collect(); - let bounds = bounds * scale_factor; - - let text_areas = - sections - .iter() - .zip(keys.iter()) - .filter_map(|(section, key)| { - let entry = cache.get(key).expect("Get cached buffer"); - - let x = section.bounds.x * scale_factor; - let y = section.bounds.y * scale_factor; - - let max_width = entry.bounds.width * scale_factor; - let total_height = entry.bounds.height * scale_factor; - - let left = match section.horizontal_alignment { - alignment::Horizontal::Left => x, - alignment::Horizontal::Center => x - max_width / 2.0, - alignment::Horizontal::Right => x - max_width, - }; - - let top = match section.vertical_alignment { - alignment::Vertical::Top => y, - alignment::Vertical::Center => y - total_height / 2.0, - alignment::Vertical::Bottom => y - total_height, - }; - - let section_bounds = Rectangle { - x: left, - y: top, - width: section.bounds.width * scale_factor, - height: section.bounds.height * scale_factor, - }; - - let clip_bounds = bounds.intersection(§ion_bounds)?; - - Some(glyphon::TextArea { - buffer: &entry.buffer, - left, - top, - scale: scale_factor, - bounds: glyphon::TextBounds { - left: clip_bounds.x as i32, - top: clip_bounds.y as i32, - right: (clip_bounds.x + clip_bounds.width) as i32, - bottom: (clip_bounds.y + clip_bounds.height) as i32, - }, - default_color: { - let [r, g, b, a] = - color::pack(section.color).components(); - - glyphon::Color::rgba( - (r * 255.0) as u8, - (g * 255.0) as u8, - (b * 255.0) as u8, - (a * 255.0) as u8, - ) - }, - }) - }); + let layer_bounds = bounds * scale_factor; + + let text_areas = sections.iter().zip(allocations.iter()).filter_map( + |(section, allocation)| { + let ( + buffer, + bounds, + horizontal_alignment, + vertical_alignment, + color, + ) = match section { + Text::Managed { + position, color, .. + } => { + use crate::core::text::Paragraph as _; + + let Some(Allocation::Paragraph(paragraph)) = allocation + else { + return None; + }; + + ( + paragraph.buffer(), + Rectangle::new(*position, paragraph.min_bounds()), + paragraph.horizontal_alignment(), + paragraph.vertical_alignment(), + *color, + ) + } + Text::Cached(text) => { + let Some(Allocation::Cache(key)) = allocation else { + return None; + }; + + let buffer = cache.get(key).expect("Get cached buffer"); + + ( + buffer, + text.bounds, + text.horizontal_alignment, + text.vertical_alignment, + text.color, + ) + } + }; + + let x = bounds.x * scale_factor; + let y = bounds.y * scale_factor; + + let max_width = bounds.width * scale_factor; + let total_height = bounds.height * scale_factor; + + let left = match horizontal_alignment { + alignment::Horizontal::Left => x, + alignment::Horizontal::Center => x - max_width / 2.0, + alignment::Horizontal::Right => x - max_width, + }; + + let top = match vertical_alignment { + alignment::Vertical::Top => y, + alignment::Vertical::Center => y - total_height / 2.0, + alignment::Vertical::Bottom => y - total_height, + }; + + let section_bounds = Rectangle { + x: left, + y: top, + width: max_width, + height: total_height, + }; + + let clip_bounds = layer_bounds.intersection(§ion_bounds)?; + + Some(glyphon::TextArea { + buffer, + left, + top, + scale: scale_factor, + bounds: glyphon::TextBounds { + left: clip_bounds.x as i32, + top: clip_bounds.y as i32, + right: (clip_bounds.x + clip_bounds.width) as i32, + bottom: (clip_bounds.y + clip_bounds.height) as i32, + }, + default_color: { + let [r, g, b, a] = color::pack(color).components(); + + glyphon::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) + }, + }) + }, + ); let result = renderer.prepare( device, @@ -219,287 +255,8 @@ impl Pipeline { pub fn end_frame(&mut self) { self.atlas.trim(); + self.cache.get_mut().trim(); self.prepare_layer = 0; } - - pub fn trim_measurements(&mut self) { - self.cache.get_mut().trim(Purpose::Measuring); - } - - pub fn measure( - &self, - content: &str, - size: f32, - line_height: LineHeight, - font: Font, - bounds: Size, - shaping: Shaping, - ) -> Size { - let mut cache = self.cache.borrow_mut(); - - let line_height = f32::from(line_height.to_absolute(Pixels(size))); - - let (_, entry) = cache.allocate( - &mut self.font_system.borrow_mut(), - Key { - content, - size, - line_height, - font, - bounds, - shaping, - }, - Purpose::Measuring, - ); - - entry.bounds - } - - pub fn hit_test( - &self, - content: &str, - size: f32, - line_height: LineHeight, - font: Font, - bounds: Size, - shaping: Shaping, - point: Point, - _nearest_only: bool, - ) -> Option { - let mut cache = self.cache.borrow_mut(); - - let line_height = f32::from(line_height.to_absolute(Pixels(size))); - - let (_, entry) = cache.allocate( - &mut self.font_system.borrow_mut(), - Key { - content, - size, - line_height, - font, - bounds, - shaping, - }, - Purpose::Measuring, - ); - - let cursor = entry.buffer.hit(point.x, point.y)?; - - Some(Hit::CharOffset(cursor.index)) - } } - -fn measure(buffer: &glyphon::Buffer) -> Size { - let (width, total_lines) = buffer - .layout_runs() - .fold((0.0, 0usize), |(width, total_lines), run| { - (run.line_w.max(width), total_lines + 1) - }); - - Size::new(width, total_lines as f32 * buffer.metrics().line_height) -} - -fn to_family(family: font::Family) -> glyphon::Family<'static> { - match family { - font::Family::Name(name) => glyphon::Family::Name(name), - font::Family::SansSerif => glyphon::Family::SansSerif, - font::Family::Serif => glyphon::Family::Serif, - font::Family::Cursive => glyphon::Family::Cursive, - font::Family::Fantasy => glyphon::Family::Fantasy, - font::Family::Monospace => glyphon::Family::Monospace, - } -} - -fn to_weight(weight: font::Weight) -> glyphon::Weight { - match weight { - font::Weight::Thin => glyphon::Weight::THIN, - font::Weight::ExtraLight => glyphon::Weight::EXTRA_LIGHT, - font::Weight::Light => glyphon::Weight::LIGHT, - font::Weight::Normal => glyphon::Weight::NORMAL, - font::Weight::Medium => glyphon::Weight::MEDIUM, - font::Weight::Semibold => glyphon::Weight::SEMIBOLD, - font::Weight::Bold => glyphon::Weight::BOLD, - font::Weight::ExtraBold => glyphon::Weight::EXTRA_BOLD, - font::Weight::Black => glyphon::Weight::BLACK, - } -} - -fn to_stretch(stretch: font::Stretch) -> glyphon::Stretch { - match stretch { - font::Stretch::UltraCondensed => glyphon::Stretch::UltraCondensed, - font::Stretch::ExtraCondensed => glyphon::Stretch::ExtraCondensed, - font::Stretch::Condensed => glyphon::Stretch::Condensed, - font::Stretch::SemiCondensed => glyphon::Stretch::SemiCondensed, - font::Stretch::Normal => glyphon::Stretch::Normal, - font::Stretch::SemiExpanded => glyphon::Stretch::SemiExpanded, - font::Stretch::Expanded => glyphon::Stretch::Expanded, - font::Stretch::ExtraExpanded => glyphon::Stretch::ExtraExpanded, - font::Stretch::UltraExpanded => glyphon::Stretch::UltraExpanded, - } -} - -fn to_style(style: font::Style) -> glyphon::Style { - match style { - font::Style::Normal => glyphon::Style::Normal, - font::Style::Italic => glyphon::Style::Italic, - font::Style::Oblique => glyphon::Style::Oblique, - } -} - -fn to_shaping(shaping: Shaping) -> glyphon::Shaping { - match shaping { - Shaping::Basic => glyphon::Shaping::Basic, - Shaping::Advanced => glyphon::Shaping::Advanced, - } -} - -struct Cache { - entries: FxHashMap, - aliases: FxHashMap, - recently_measured: FxHashSet, - recently_drawn: FxHashSet, - hasher: HashBuilder, -} - -struct Entry { - buffer: glyphon::Buffer, - bounds: Size, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Purpose { - Measuring, - Drawing, -} - -#[cfg(not(target_arch = "wasm32"))] -type HashBuilder = twox_hash::RandomXxHashBuilder64; - -#[cfg(target_arch = "wasm32")] -type HashBuilder = std::hash::BuildHasherDefault; - -impl Cache { - fn new() -> Self { - Self { - entries: FxHashMap::default(), - aliases: FxHashMap::default(), - recently_measured: FxHashSet::default(), - recently_drawn: FxHashSet::default(), - hasher: HashBuilder::default(), - } - } - - fn get(&self, key: &KeyHash) -> Option<&Entry> { - self.entries.get(key) - } - - fn allocate( - &mut self, - font_system: &mut glyphon::FontSystem, - key: Key<'_>, - purpose: Purpose, - ) -> (KeyHash, &mut Entry) { - let hash = key.hash(self.hasher.build_hasher()); - - let recently_used = match purpose { - Purpose::Measuring => &mut self.recently_measured, - Purpose::Drawing => &mut self.recently_drawn, - }; - - if let Some(hash) = self.aliases.get(&hash) { - let _ = recently_used.insert(*hash); - - return (*hash, self.entries.get_mut(hash).unwrap()); - } - - if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { - let metrics = glyphon::Metrics::new(key.size, key.line_height); - let mut buffer = glyphon::Buffer::new(font_system, metrics); - - buffer.set_size( - font_system, - key.bounds.width, - key.bounds.height.max(key.line_height), - ); - buffer.set_text( - font_system, - key.content, - glyphon::Attrs::new() - .family(to_family(key.font.family)) - .weight(to_weight(key.font.weight)) - .stretch(to_stretch(key.font.stretch)) - .style(to_style(key.font.style)), - to_shaping(key.shaping), - ); - - let bounds = measure(&buffer); - let _ = entry.insert(Entry { buffer, bounds }); - - for bounds in [ - bounds, - Size { - width: key.bounds.width, - ..bounds - }, - ] { - if key.bounds != bounds { - let _ = self.aliases.insert( - Key { bounds, ..key }.hash(self.hasher.build_hasher()), - hash, - ); - } - } - } - - let _ = recently_used.insert(hash); - - (hash, self.entries.get_mut(&hash).unwrap()) - } - - fn trim(&mut self, purpose: Purpose) { - self.entries.retain(|key, _| { - self.recently_measured.contains(key) - || self.recently_drawn.contains(key) - }); - self.aliases.retain(|_, value| { - self.recently_measured.contains(value) - || self.recently_drawn.contains(value) - }); - - match purpose { - Purpose::Measuring => { - self.recently_measured.clear(); - } - Purpose::Drawing => { - self.recently_drawn.clear(); - } - } - } -} - -#[derive(Debug, Clone, Copy)] -struct Key<'a> { - content: &'a str, - size: f32, - line_height: f32, - font: Font, - bounds: Size, - shaping: Shaping, -} - -impl Key<'_> { - fn hash(self, mut hasher: H) -> KeyHash { - self.content.hash(&mut hasher); - self.size.to_bits().hash(&mut hasher); - self.line_height.to_bits().hash(&mut hasher); - self.font.hash(&mut hasher); - self.bounds.width.to_bits().hash(&mut hasher); - self.bounds.height.to_bits().hash(&mut hasher); - self.shaping.hash(&mut hasher); - - hasher.finish() - } -} - -type KeyHash = u64; diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index cd5b20cc..09e11fdc 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -216,7 +216,14 @@ impl graphics::Compositor for Compositor { ) -> Result<(Self, Self::Renderer), Error> { let (compositor, backend) = new(settings, compatible_window)?; - Ok((compositor, Renderer::new(backend))) + Ok(( + compositor, + Renderer::new( + backend, + settings.default_font, + settings.default_text_size, + ), + )) } fn create_surface( diff --git a/widget/src/button.rs b/widget/src/button.rs index 5727c631..1788b6c4 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -159,19 +159,15 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - layout( - renderer, - limits, - self.width, - self.height, - self.padding, - |renderer, limits| { - self.content.as_widget().layout(renderer, limits) - }, - ) + layout(limits, self.width, self.height, self.padding, |limits| { + self.content + .as_widget() + .layout(&tree.children[0], renderer, limits) + }) } fn operate( @@ -426,17 +422,16 @@ where } /// Computes the layout of a [`Button`]. -pub fn layout( - renderer: &Renderer, +pub fn layout( limits: &layout::Limits, width: Length, height: Length, padding: Padding, - layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, + layout_content: impl FnOnce(&layout::Limits) -> layout::Node, ) -> layout::Node { let limits = limits.width(width).height(height); - let mut content = layout_content(renderer, &limits.pad(padding)); + let mut content = layout_content(&limits.pad(padding)); let padding = padding.fit(content.size(), limits.max()); let size = limits.pad(padding).resolve(content.size()).pad(padding); diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 1a186432..d749355b 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -129,6 +129,7 @@ where fn layout( &self, + _tree: &Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 310a67ed..a66ce3ff 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -6,12 +6,11 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::text; use crate::core::touch; -use crate::core::widget::Tree; +use crate::core::widget; +use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Alignment, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, - Widget, + Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, }; -use crate::{Row, Text}; pub use iced_style::checkbox::{Appearance, StyleSheet}; @@ -45,7 +44,7 @@ where width: Length, size: f32, spacing: f32, - text_size: Option, + text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option, @@ -118,7 +117,7 @@ where /// Sets the text size of the [`Checkbox`]. pub fn text_size(mut self, text_size: impl Into) -> Self { - self.text_size = Some(text_size.into().0); + self.text_size = Some(text_size.into()); self } @@ -167,6 +166,14 @@ where Renderer: text::Renderer, Renderer::Theme: StyleSheet + crate::text::StyleSheet, { + fn tag(&self) -> tree::Tag { + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(widget::text::State::::default()) + } + fn width(&self) -> Length { self.width } @@ -177,26 +184,35 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - Row::<(), Renderer>::new() - .width(self.width) - .spacing(self.spacing) - .align_items(Alignment::Center) - .push(Row::new().width(self.size).height(self.size)) - .push( - Text::new(&self.label) - .font(self.font.unwrap_or_else(|| renderer.default_font())) - .width(self.width) - .size( - self.text_size - .unwrap_or_else(|| renderer.default_size()), - ) - .line_height(self.text_line_height) - .shaping(self.text_shaping), - ) - .layout(renderer, limits) + layout::next_to_each_other( + &limits.width(self.width), + self.spacing, + |_| layout::Node::new(Size::new(self.size, self.size)), + |limits| { + let state = tree + .state + .downcast_ref::>(); + + widget::text::layout( + state, + renderer, + limits, + self.width, + Length::Shrink, + &self.label, + self.text_line_height, + self.text_size, + self.font, + alignment::Horizontal::Left, + alignment::Vertical::Top, + self.text_shaping, + ) + }, + ) } fn on_event( @@ -244,7 +260,7 @@ where fn draw( &self, - _tree: &Tree, + tree: &Tree, renderer: &mut Renderer, theme: &Renderer::Theme, style: &renderer::Style, @@ -283,24 +299,23 @@ where line_height, shaping, } = &self.icon; - let size = size.unwrap_or(bounds.height * 0.7); + let size = size.unwrap_or(Pixels(bounds.height * 0.7)); if self.is_checked { - renderer.fill_text(text::Text { - content: &code_point.to_string(), - font: *font, - size, - line_height: *line_height, - bounds: Rectangle { - x: bounds.center_x(), - y: bounds.center_y(), - ..bounds + renderer.fill_text( + text::Text { + content: &code_point.to_string(), + font: *font, + size, + line_height: *line_height, + bounds: bounds.size(), + horizontal_alignment: alignment::Horizontal::Center, + vertical_alignment: alignment::Vertical::Center, + shaping: *shaping, }, - color: custom_style.icon_color, - horizontal_alignment: alignment::Horizontal::Center, - vertical_alignment: alignment::Vertical::Center, - shaping: *shaping, - }); + bounds.center(), + custom_style.icon_color, + ); } } @@ -311,16 +326,10 @@ where renderer, style, label_layout, - &self.label, - self.text_size, - self.text_line_height, - self.font, + tree.state.downcast_ref(), crate::text::Appearance { color: custom_style.text_color, }, - alignment::Horizontal::Left, - alignment::Vertical::Center, - self.text_shaping, ); } } @@ -348,7 +357,7 @@ pub struct Icon { /// The unicode code point that will be used as the icon. pub code_point: char, /// Font size of the content. - pub size: Option, + pub size: Option, /// The line height of the icon. pub line_height: text::LineHeight, /// The shaping strategy of the icon. diff --git a/widget/src/column.rs b/widget/src/column.rs index c16477f3..107c3475 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -122,6 +122,7 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -138,6 +139,7 @@ where self.spacing, self.align_items, &self.children, + &tree.children, ) } diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 690ef27c..8c20ae8e 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -144,11 +144,6 @@ where 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); @@ -179,7 +174,6 @@ pub struct State(RefCell>); #[derive(Debug, Clone)] struct Inner { - text_input: text_input::State, value: String, options: Vec, option_matchers: Vec, @@ -216,7 +210,6 @@ where ); Self(RefCell::new(Inner { - text_input: text_input::State::new(), value, options, option_matchers, @@ -224,51 +217,12 @@ where })) } - /// 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(); @@ -288,21 +242,6 @@ where } } -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, @@ -366,10 +305,11 @@ where fn layout( &self, + tree: &widget::Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.text_input.layout(renderer, limits) + self.text_input.layout(tree, renderer, limits) } fn tag(&self) -> widget::tree::Tag { @@ -385,6 +325,10 @@ where }) } + fn children(&self) -> Vec { + vec![widget::Tree::new(&self.text_input as &dyn Widget<_, _>)] + } + fn on_event( &mut self, tree: &mut widget::Tree, @@ -398,7 +342,13 @@ where ) -> event::Status { let menu = tree.state.downcast_mut::>(); - let started_focused = self.state.is_focused(); + let started_focused = { + let text_input_state = tree.children[0] + .state + .downcast_ref::>(); + + text_input_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; @@ -408,9 +358,8 @@ where 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, + &mut tree.children[0], event.clone(), layout, cursor, @@ -419,7 +368,6 @@ where &mut local_shell, viewport, ); - self.state.update_text_input(tree); // Then finally react to them here for message in local_messages { @@ -450,7 +398,15 @@ where shell.invalidate_layout(); } - if self.state.is_focused() { + let is_focused = { + let text_input_state = tree.children[0] + .state + .downcast_ref::>(); + + text_input_state.is_focused() + }; + + if is_focused { self.state.with_inner(|state| { if !started_focused { if let Some(on_option_hovered) = &mut self.on_option_hovered @@ -589,9 +545,8 @@ where published_message_to_shell = true; // Unfocus the input - let mut tree = state.text_input_tree(); let _ = self.text_input.on_event( - &mut tree, + &mut tree.children[0], Event::Mouse(mouse::Event::ButtonPressed( mouse::Button::Left, )), @@ -602,21 +557,25 @@ where &mut Shell::new(&mut vec![]), viewport, ); - state.update_text_input(tree); } }); - if started_focused - && !self.state.is_focused() - && !published_message_to_shell - { + let is_focused = { + let text_input_state = tree.children[0] + .state + .downcast_ref::>(); + + text_input_state.is_focused() + }; + + if started_focused && !is_focused && !published_message_to_shell { if let Some(message) = self.on_close.take() { shell.publish(message); } } // Focus changed, invalidate widget tree to force a fresh `view` - if started_focused != self.state.is_focused() { + if started_focused != is_focused { shell.invalidate_widgets(); } @@ -625,20 +584,24 @@ where fn mouse_interaction( &self, - _tree: &widget::Tree, + 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) + self.text_input.mouse_interaction( + &tree.children[0], + layout, + cursor, + viewport, + renderer, + ) } fn draw( &self, - _tree: &widget::Tree, + tree: &widget::Tree, renderer: &mut Renderer, theme: &Renderer::Theme, _style: &renderer::Style, @@ -646,16 +609,28 @@ where cursor: mouse::Cursor, _viewport: &Rectangle, ) { - let selection = if self.state.is_focused() || self.selection.is_empty() - { + let is_focused = { + let text_input_state = tree.children[0] + .state + .downcast_ref::>(); + + text_input_state.is_focused() + }; + + let selection = if 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); + self.text_input.draw( + &tree.children[0], + renderer, + theme, + layout, + cursor, + selection, + ); } fn overlay<'b>( @@ -664,14 +639,22 @@ where layout: Layout<'_>, _renderer: &Renderer, ) -> Option> { - let Menu { - menu, - filtered_options, - hovered_option, - .. - } = tree.state.downcast_mut::>(); + let is_focused = { + let text_input_state = tree.children[0] + .state + .downcast_ref::>(); + + text_input_state.is_focused() + }; + + if is_focused { + 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); diff --git a/widget/src/container.rs b/widget/src/container.rs index 1f1df861..c16c1117 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -5,7 +5,8 @@ use crate::core::layout; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; -use crate::core::widget::{self, Operation, Tree}; +use crate::core::widget::tree::{self, Tree}; +use crate::core::widget::{self, Operation}; use crate::core::{ Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Vector, Widget, @@ -135,12 +136,20 @@ where Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { + fn tag(&self) -> tree::Tag { + self.content.as_widget().tag() + } + + fn state(&self) -> tree::State { + self.content.as_widget().state() + } + fn children(&self) -> Vec { - vec![Tree::new(&self.content)] + self.content.as_widget().children() } fn diff(&self, tree: &mut Tree) { - tree.diff_children(std::slice::from_ref(&self.content)) + self.content.as_widget().diff(tree); } fn width(&self) -> Length { @@ -153,11 +162,11 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { layout( - renderer, limits, self.width, self.height, @@ -166,9 +175,7 @@ where self.padding, self.horizontal_alignment, self.vertical_alignment, - |renderer, limits| { - self.content.as_widget().layout(renderer, limits) - }, + |limits| self.content.as_widget().layout(tree, renderer, limits), ) } @@ -184,7 +191,7 @@ where layout.bounds(), &mut |operation| { self.content.as_widget().operate( - &mut tree.children[0], + tree, layout.children().next().unwrap(), renderer, operation, @@ -205,7 +212,7 @@ where viewport: &Rectangle, ) -> event::Status { self.content.as_widget_mut().on_event( - &mut tree.children[0], + tree, event, layout.children().next().unwrap(), cursor, @@ -225,7 +232,7 @@ where renderer: &Renderer, ) -> mouse::Interaction { self.content.as_widget().mouse_interaction( - &tree.children[0], + tree, layout.children().next().unwrap(), cursor, viewport, @@ -248,7 +255,7 @@ where draw_background(renderer, &style, layout.bounds()); self.content.as_widget().draw( - &tree.children[0], + tree, renderer, theme, &renderer::Style { @@ -269,7 +276,7 @@ where renderer: &Renderer, ) -> Option> { self.content.as_widget_mut().overlay( - &mut tree.children[0], + tree, layout.children().next().unwrap(), renderer, ) @@ -291,8 +298,7 @@ where } /// Computes the layout of a [`Container`]. -pub fn layout( - renderer: &Renderer, +pub fn layout( limits: &layout::Limits, width: Length, height: Length, @@ -301,7 +307,7 @@ pub fn layout( padding: Padding, horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, - layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, + layout_content: impl FnOnce(&layout::Limits) -> layout::Node, ) -> layout::Node { let limits = limits .loose() @@ -310,7 +316,7 @@ pub fn layout( .width(width) .height(height); - let mut content = layout_content(renderer, &limits.pad(padding).loose()); + let mut content = layout_content(&limits.pad(padding).loose()); let padding = padding.fit(content.size(), limits.max()); let size = limits.pad(padding).resolve(content.size()); diff --git a/widget/src/image.rs b/widget/src/image.rs index 66bf2156..f73ee5d7 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -167,6 +167,7 @@ where fn layout( &self, + _tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 6e095667..1f52bf2f 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -105,6 +105,7 @@ where fn layout( &self, + _tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 761f45ad..412254f5 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -152,11 +152,12 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { self.with_element(|element| { - element.as_widget().layout(renderer, limits) + element.as_widget().layout(tree, renderer, limits) }) } diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index 19df2792..9b3b13b2 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -254,11 +254,12 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { self.with_element(|element| { - element.as_widget().layout(renderer, limits) + element.as_widget().layout(tree, renderer, limits) }) } diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index b56545c8..5ab8ed1a 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -60,13 +60,13 @@ impl<'a, Message, Renderer> Content<'a, Message, Renderer> where Renderer: core::Renderer, { - fn layout(&mut self, renderer: &Renderer) { + fn layout(&mut self, tree: &Tree, renderer: &Renderer) { if self.layout.is_none() { - self.layout = - Some(self.element.as_widget().layout( - renderer, - &layout::Limits::new(Size::ZERO, self.size), - )); + self.layout = Some(self.element.as_widget().layout( + tree, + renderer, + &layout::Limits::new(Size::ZERO, self.size), + )); } } @@ -104,7 +104,7 @@ where R: Deref, { self.update(tree, layout.bounds().size(), view); - self.layout(renderer.deref()); + self.layout(tree, renderer.deref()); let content_layout = Layout::with_offset( layout.position() - Point::ORIGIN, @@ -144,6 +144,7 @@ where fn layout( &self, + _tree: &Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -285,7 +286,7 @@ where overlay_builder: |content: &mut RefMut<'_, Content<'_, _, _>>, tree| { content.update(tree, layout.bounds().size(), &self.view); - content.layout(renderer); + content.layout(tree, renderer); let Content { element, diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 490f7c48..95b45b02 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -120,10 +120,11 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.content.as_widget().layout(renderer, limits) + self.content.as_widget().layout(tree, renderer, limits) } fn operate( diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index f7bdeef6..71703e71 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -31,7 +31,7 @@ where on_option_hovered: Option<&'a dyn Fn(T) -> Message>, width: f32, padding: Padding, - text_size: Option, + text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option, @@ -85,7 +85,7 @@ where /// Sets the text size of the [`Menu`]. pub fn text_size(mut self, text_size: impl Into) -> Self { - self.text_size = Some(text_size.into().0); + self.text_size = Some(text_size.into()); self } @@ -253,7 +253,7 @@ where ) .width(self.width); - let mut node = self.container.layout(renderer, &limits); + let mut node = self.container.layout(self.state, renderer, &limits); node.move_to(if space_below > space_above { position + Vector::new(0.0, self.target_height) @@ -328,7 +328,7 @@ where on_selected: Box Message + 'a>, on_option_hovered: Option<&'a dyn Fn(T) -> Message>, padding: Padding, - text_size: Option, + text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option, @@ -352,6 +352,7 @@ where fn layout( &self, + _tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -361,8 +362,7 @@ where let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()); - let text_line_height = - self.text_line_height.to_absolute(Pixels(text_size)); + let text_line_height = self.text_line_height.to_absolute(text_size); let size = { let intrinsic = Size::new( @@ -407,9 +407,9 @@ where .text_size .unwrap_or_else(|| renderer.default_size()); - let option_height = f32::from( - self.text_line_height.to_absolute(Pixels(text_size)), - ) + self.padding.vertical(); + let option_height = + f32::from(self.text_line_height.to_absolute(text_size)) + + self.padding.vertical(); let new_hovered_option = (cursor_position.y / option_height) as usize; @@ -436,9 +436,9 @@ where .text_size .unwrap_or_else(|| renderer.default_size()); - let option_height = f32::from( - self.text_line_height.to_absolute(Pixels(text_size)), - ) + self.padding.vertical(); + let option_height = + f32::from(self.text_line_height.to_absolute(text_size)) + + self.padding.vertical(); *self.hovered_option = Some((cursor_position.y / option_height) as usize); @@ -490,7 +490,7 @@ where let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()); let option_height = - f32::from(self.text_line_height.to_absolute(Pixels(text_size))) + f32::from(self.text_line_height.to_absolute(text_size)) + self.padding.vertical(); let offset = viewport.y - bounds.y; @@ -526,26 +526,24 @@ where ); } - renderer.fill_text(Text { - content: &option.to_string(), - bounds: Rectangle { - x: bounds.x + self.padding.left, - y: bounds.center_y(), - width: f32::INFINITY, - ..bounds + renderer.fill_text( + Text { + content: &option.to_string(), + bounds: Size::new(f32::INFINITY, bounds.height), + size: text_size, + line_height: self.text_line_height, + font: self.font.unwrap_or_else(|| renderer.default_font()), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: self.text_shaping, }, - size: text_size, - line_height: self.text_line_height, - font: self.font.unwrap_or_else(|| renderer.default_font()), - color: if is_selected { + Point::new(bounds.x + self.padding.left, bounds.center_y()), + if is_selected { appearance.selected_text_color } else { appearance.text_color }, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - shaping: self.text_shaping, - }); + ); } } } diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index d8c98858..366d9a66 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -275,10 +275,12 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { layout( + tree, renderer, limits, self.contents.layout(), @@ -286,7 +288,9 @@ where self.height, self.spacing, self.contents.iter(), - |content, renderer, limits| content.layout(renderer, limits), + |content, tree, renderer, limits| { + content.layout(tree, renderer, limits) + }, ) } @@ -471,6 +475,7 @@ where /// Calculates the [`Layout`] of a [`PaneGrid`]. pub fn layout( + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, node: &Node, @@ -478,19 +483,21 @@ pub fn layout( height: Length, spacing: f32, contents: impl Iterator, - layout_content: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node, + layout_content: impl Fn(T, &Tree, &Renderer, &layout::Limits) -> layout::Node, ) -> layout::Node { let limits = limits.width(width).height(height); let size = limits.resolve(Size::ZERO); let regions = node.pane_regions(spacing, size); let children = contents - .filter_map(|(pane, content)| { + .zip(tree.children.iter()) + .filter_map(|((pane, content), tree)| { let region = regions.get(&pane)?; let size = Size::new(region.width, region.height); let mut node = layout_content( content, + tree, renderer, &layout::Limits::new(size, size), ); diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index e890e41a..8a74b4b9 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -150,18 +150,23 @@ where pub(crate) fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { if let Some(title_bar) = &self.title_bar { let max_size = limits.max(); - let title_bar_layout = title_bar - .layout(renderer, &layout::Limits::new(Size::ZERO, max_size)); + let title_bar_layout = title_bar.layout( + &tree.children[1], + renderer, + &layout::Limits::new(Size::ZERO, max_size), + ); let title_bar_size = title_bar_layout.size(); let mut body_layout = self.body.as_widget().layout( + &tree.children[0], renderer, &layout::Limits::new( Size::ZERO, @@ -179,7 +184,9 @@ where vec![title_bar_layout, body_layout], ) } else { - self.body.as_widget().layout(renderer, limits) + self.body + .as_widget() + .layout(&tree.children[0], renderer, limits) } } diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index cac24e68..c0fb9936 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -213,23 +213,27 @@ where pub(crate) fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let limits = limits.pad(self.padding); let max_size = limits.max(); - let title_layout = self - .content - .as_widget() - .layout(renderer, &layout::Limits::new(Size::ZERO, max_size)); + let title_layout = self.content.as_widget().layout( + &tree.children[0], + renderer, + &layout::Limits::new(Size::ZERO, max_size), + ); let title_size = title_layout.size(); let mut node = if let Some(controls) = &self.controls { - let mut controls_layout = controls - .as_widget() - .layout(renderer, &layout::Limits::new(Size::ZERO, max_size)); + let mut controls_layout = controls.as_widget().layout( + &tree.children[1], + renderer, + &layout::Limits::new(Size::ZERO, max_size), + ); let controls_size = controls_layout.size(); let space_before_controls = max_size.width - controls_size.width; diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 0a1e2a99..719aa066 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -7,17 +7,18 @@ use crate::core::layout; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; -use crate::core::text::{self, Text}; +use crate::core::text::{self, Paragraph as _, Text}; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, - Size, Widget, + Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle, + Shell, Size, Widget, }; use crate::overlay::menu::{self, Menu}; use crate::scrollable; use std::borrow::Cow; +use std::cell::RefCell; pub use crate::style::pick_list::{Appearance, StyleSheet}; @@ -35,7 +36,7 @@ where selected: Option, width: Length, padding: Padding, - text_size: Option, + text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option, @@ -101,7 +102,7 @@ where /// Sets the text size of the [`PickList`]. pub fn text_size(mut self, size: impl Into) -> Self { - self.text_size = Some(size.into().0); + self.text_size = Some(size.into()); self } @@ -157,11 +158,11 @@ where From<::Style>, { fn tag(&self) -> tree::Tag { - tree::Tag::of::() + tree::Tag::of::>() } fn state(&self) -> tree::State { - tree::State::new(State::new()) + tree::State::new(State::::new()) } fn width(&self) -> Length { @@ -174,10 +175,12 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { layout( + tree.state.downcast_ref::>(), renderer, limits, self.width, @@ -210,7 +213,7 @@ where self.on_selected.as_ref(), self.selected.as_ref(), &self.options, - || tree.state.downcast_mut::(), + || tree.state.downcast_mut::>(), ) } @@ -250,7 +253,7 @@ where self.selected.as_ref(), &self.handle, &self.style, - || tree.state.downcast_ref::(), + || tree.state.downcast_ref::>(), ) } @@ -260,7 +263,7 @@ where layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - let state = tree.state.downcast_mut::(); + let state = tree.state.downcast_mut::>(); overlay( layout, @@ -295,28 +298,32 @@ where } } -/// The local state of a [`PickList`]. +/// The state of a [`PickList`]. #[derive(Debug)] -pub struct State { +pub struct State { menu: menu::State, keyboard_modifiers: keyboard::Modifiers, is_open: bool, hovered_option: Option, + option_paragraphs: RefCell>, + placeholder_paragraph: RefCell

, } -impl State { +impl State

{ /// Creates a new [`State`] for a [`PickList`]. - pub fn new() -> Self { + fn new() -> Self { Self { menu: menu::State::default(), keyboard_modifiers: keyboard::Modifiers::default(), is_open: bool::default(), hovered_option: Option::default(), + option_paragraphs: RefCell::new(Vec::new()), + placeholder_paragraph: RefCell::new(Default::default()), } } } -impl Default for State { +impl Default for State

{ fn default() -> Self { Self::new() } @@ -330,7 +337,7 @@ pub enum Handle { /// This is the default. Arrow { /// Font size of the content. - size: Option, + size: Option, }, /// A custom static handle. Static(Icon), @@ -359,7 +366,7 @@ pub struct Icon { /// The unicode code point that will be used as the icon. pub code_point: char, /// Font size of the content. - pub size: Option, + pub size: Option, /// Line height of the content. pub line_height: text::LineHeight, /// The shaping strategy of the icon. @@ -368,11 +375,12 @@ pub struct Icon { /// Computes the layout of a [`PickList`]. pub fn layout( + state: &State, renderer: &Renderer, limits: &layout::Limits, width: Length, padding: Padding, - text_size: Option, + text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option, @@ -386,38 +394,70 @@ where use std::f32; let limits = limits.width(width).height(Length::Shrink).pad(padding); + let font = font.unwrap_or_else(|| renderer.default_font()); let text_size = text_size.unwrap_or_else(|| renderer.default_size()); - let max_width = match width { - Length::Shrink => { - let measure = |label: &str| -> f32 { - let width = renderer.measure_width( - label, - text_size, - font.unwrap_or_else(|| renderer.default_font()), - text_shaping, - ); - - width.round() - }; + let mut paragraphs = state.option_paragraphs.borrow_mut(); + + paragraphs.resize_with(options.len(), Default::default); + + let option_text = Text { + content: "", + bounds: Size::new( + f32::INFINITY, + text_line_height.to_absolute(text_size).into(), + ), + size: text_size, + line_height: text_line_height, + font, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: text_shaping, + }; - let labels = options.iter().map(ToString::to_string); + for (option, paragraph) in options.iter().zip(paragraphs.iter_mut()) { + let label = option.to_string(); - let labels_width = labels - .map(|label| measure(&label)) - .fold(100.0, |candidate, current| current.max(candidate)); + renderer.update_paragraph( + paragraph, + Text { + content: &label, + ..option_text + }, + ); + } - let placeholder_width = placeholder.map(measure).unwrap_or(100.0); + if let Some(placeholder) = placeholder { + let mut paragraph = state.placeholder_paragraph.borrow_mut(); + renderer.update_paragraph( + &mut paragraph, + Text { + content: placeholder, + ..option_text + }, + ); + } - labels_width.max(placeholder_width) + let max_width = match width { + Length::Shrink => { + let labels_width = + paragraphs.iter().fold(0.0, |width, paragraph| { + f32::max(width, paragraph.min_width()) + }); + + labels_width.max( + placeholder + .map(|_| state.placeholder_paragraph.borrow().min_width()) + .unwrap_or(0.0), + ) } _ => 0.0, }; let size = { let intrinsic = Size::new( - max_width + text_size + padding.left, - f32::from(text_line_height.to_absolute(Pixels(text_size))), + max_width + text_size.0 + padding.left, + f32::from(text_line_height.to_absolute(text_size)), ); limits.resolve(intrinsic).pad(padding) @@ -428,7 +468,7 @@ where /// Processes an [`Event`] and updates the [`State`] of a [`PickList`] /// accordingly. -pub fn update<'a, T, Message>( +pub fn update<'a, T, P, Message>( event: Event, layout: Layout<'_>, cursor: mouse::Cursor, @@ -436,10 +476,11 @@ pub fn update<'a, T, Message>( on_selected: &dyn Fn(T) -> Message, selected: Option<&T>, options: &[T], - state: impl FnOnce() -> &'a mut State, + state: impl FnOnce() -> &'a mut State

, ) -> event::Status where T: PartialEq + Clone + 'a, + P: text::Paragraph + 'a, { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) @@ -534,9 +575,9 @@ pub fn mouse_interaction( /// Returns the current overlay of a [`PickList`]. pub fn overlay<'a, T, Message, Renderer>( layout: Layout<'_>, - state: &'a mut State, + state: &'a mut State, padding: Padding, - text_size: Option, + text_size: Option, text_shaping: text::Shaping, font: Renderer::Font, options: &'a [T], @@ -591,7 +632,7 @@ pub fn draw<'a, T, Renderer>( layout: Layout<'_>, cursor: mouse::Cursor, padding: Padding, - text_size: Option, + text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Renderer::Font, @@ -599,7 +640,7 @@ pub fn draw<'a, T, Renderer>( selected: Option<&T>, handle: &Handle, style: &::Style, - state: impl FnOnce() -> &'a State, + state: impl FnOnce() -> &'a State, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -665,22 +706,26 @@ pub fn draw<'a, T, Renderer>( if let Some((font, code_point, size, line_height, shaping)) = handle { let size = size.unwrap_or_else(|| renderer.default_size()); - renderer.fill_text(Text { - content: &code_point.to_string(), - size, - line_height, - font, - color: style.handle_color, - bounds: Rectangle { - x: bounds.x + bounds.width - padding.horizontal(), - y: bounds.center_y(), - height: f32::from(line_height.to_absolute(Pixels(size))), - ..bounds + renderer.fill_text( + Text { + content: &code_point.to_string(), + size, + line_height, + font, + bounds: Size::new( + bounds.width, + f32::from(line_height.to_absolute(size)), + ), + horizontal_alignment: alignment::Horizontal::Right, + vertical_alignment: alignment::Vertical::Center, + shaping, }, - horizontal_alignment: alignment::Horizontal::Right, - vertical_alignment: alignment::Vertical::Center, - shaping, - }); + Point::new( + bounds.x + bounds.width - padding.horizontal(), + bounds.center_y(), + ), + style.handle_color, + ); } let label = selected.map(ToString::to_string); @@ -688,27 +733,26 @@ pub fn draw<'a, T, Renderer>( if let Some(label) = label.as_deref().or(placeholder) { let text_size = text_size.unwrap_or_else(|| renderer.default_size()); - renderer.fill_text(Text { - content: label, - size: text_size, - line_height: text_line_height, - font, - color: if is_selected { + renderer.fill_text( + Text { + content: label, + size: text_size, + line_height: text_line_height, + font, + bounds: Size::new( + bounds.width - padding.horizontal(), + f32::from(text_line_height.to_absolute(text_size)), + ), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: text_shaping, + }, + Point::new(bounds.x + padding.left, bounds.center_y()), + if is_selected { style.text_color } else { style.placeholder_color }, - bounds: Rectangle { - x: bounds.x + padding.left, - y: bounds.center_y(), - width: bounds.width - padding.horizontal(), - height: f32::from( - text_line_height.to_absolute(Pixels(text_size)), - ), - }, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - shaping: text_shaping, - }); + ); } } diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs index 37c6bc72..8b1490af 100644 --- a/widget/src/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -95,6 +95,7 @@ where fn layout( &self, + _tree: &Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index 51a541fd..589704a5 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -60,6 +60,7 @@ impl<'a, Message, Theme> Widget> for QRCode<'a> { fn layout( &self, + _tree: &Tree, _renderer: &Renderer, _limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 65d71ec2..cb908ec4 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -6,12 +6,12 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::text; use crate::core::touch; -use crate::core::widget::Tree; +use crate::core::widget; +use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Alignment, Clipboard, Color, Element, Layout, Length, Pixels, Rectangle, - Shell, Widget, + Clipboard, Color, Element, Layout, Length, Pixels, Rectangle, Shell, Size, + Widget, }; -use crate::{Row, Text}; pub use iced_style::radio::{Appearance, StyleSheet}; @@ -80,7 +80,7 @@ where width: Length, size: f32, spacing: f32, - text_size: Option, + text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option, @@ -152,7 +152,7 @@ where /// Sets the text size of the [`Radio`] button. pub fn text_size(mut self, text_size: impl Into) -> Self { - self.text_size = Some(text_size.into().0); + self.text_size = Some(text_size.into()); self } @@ -193,6 +193,14 @@ where Renderer: text::Renderer, Renderer::Theme: StyleSheet + crate::text::StyleSheet, { + fn tag(&self) -> tree::Tag { + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(widget::text::State::::default()) + } + fn width(&self) -> Length { self.width } @@ -203,25 +211,35 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - Row::<(), Renderer>::new() - .width(self.width) - .spacing(self.spacing) - .align_items(Alignment::Center) - .push(Row::new().width(self.size).height(self.size)) - .push( - Text::new(&self.label) - .width(self.width) - .size( - self.text_size - .unwrap_or_else(|| renderer.default_size()), - ) - .line_height(self.text_line_height) - .shaping(self.text_shaping), - ) - .layout(renderer, limits) + layout::next_to_each_other( + &limits.width(self.width), + self.spacing, + |_| layout::Node::new(Size::new(self.size, self.size)), + |limits| { + let state = tree + .state + .downcast_ref::>(); + + widget::text::layout( + state, + renderer, + limits, + self.width, + Length::Shrink, + &self.label, + self.text_line_height, + self.text_size, + self.font, + alignment::Horizontal::Left, + alignment::Vertical::Top, + self.text_shaping, + ) + }, + ) } fn on_event( @@ -267,7 +285,7 @@ where fn draw( &self, - _state: &Tree, + tree: &Tree, renderer: &mut Renderer, theme: &Renderer::Theme, style: &renderer::Style, @@ -327,16 +345,10 @@ where renderer, style, label_layout, - &self.label, - self.text_size, - self.text_line_height, - self.font, + tree.state.downcast_ref(), crate::text::Appearance { color: custom_style.text_color, }, - alignment::Horizontal::Left, - alignment::Vertical::Center, - self.text_shaping, ); } } diff --git a/widget/src/row.rs b/widget/src/row.rs index 99b2a0bf..17c49e67 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -114,6 +114,7 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -127,6 +128,7 @@ where self.spacing, self.align_items, &self.children, + &tree.children, ) } diff --git a/widget/src/rule.rs b/widget/src/rule.rs index d703e6ae..032ff860 100644 --- a/widget/src/rule.rs +++ b/widget/src/rule.rs @@ -72,6 +72,7 @@ where fn layout( &self, + _tree: &Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index a83ed985..ce96883d 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -230,6 +230,7 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -240,7 +241,11 @@ where self.height, &self.direction, |renderer, limits| { - self.content.as_widget().layout(renderer, limits) + self.content.as_widget().layout( + &tree.children[0], + renderer, + limits, + ) }, ) } diff --git a/widget/src/slider.rs b/widget/src/slider.rs index e41be7c9..b4c9198a 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -169,6 +169,7 @@ where fn layout( &self, + _tree: &Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/space.rs b/widget/src/space.rs index 9a5385e8..84331870 100644 --- a/widget/src/space.rs +++ b/widget/src/space.rs @@ -55,6 +55,7 @@ where fn layout( &self, + _tree: &Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 1ccc5d62..f61a4ce2 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -106,6 +106,7 @@ where fn layout( &self, + _tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 61fc0055..209ef968 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -17,7 +17,7 @@ use crate::core::keyboard; use crate::core::layout; use crate::core::mouse::{self, click}; use crate::core::renderer; -use crate::core::text::{self, Text}; +use crate::core::text::{self, Paragraph as _, Text}; use crate::core::time::{Duration, Instant}; use crate::core::touch; use crate::core::widget; @@ -30,6 +30,8 @@ use crate::core::{ }; use crate::runtime::Command; +use std::cell::RefCell; + pub use iced_style::text_input::{Appearance, StyleSheet}; /// A field that can be filled with text. @@ -67,7 +69,7 @@ where font: Option, width: Length, padding: Padding, - size: Option, + size: Option, line_height: text::LineHeight, on_input: Option Message + 'a>>, on_paste: Option Message + 'a>>, @@ -178,7 +180,7 @@ where /// Sets the text size of the [`TextInput`]. pub fn size(mut self, size: impl Into) -> Self { - self.size = Some(size.into().0); + self.size = Some(size.into()); self } @@ -218,12 +220,8 @@ where theme, layout, cursor, - tree.state.downcast_ref::(), + tree.state.downcast_ref::>(), value.unwrap_or(&self.value), - &self.placeholder, - self.size, - self.line_height, - self.font, self.on_input.is_none(), self.is_secure, self.icon.as_ref(), @@ -240,15 +238,15 @@ where Renderer::Theme: StyleSheet, { fn tag(&self) -> tree::Tag { - tree::Tag::of::() + tree::Tag::of::>() } fn state(&self) -> tree::State { - tree::State::new(State::new()) + tree::State::new(State::::new()) } fn diff(&self, tree: &mut Tree) { - let state = tree.state.downcast_mut::(); + let state = tree.state.downcast_mut::>(); // Unfocus text input if it becomes disabled if self.on_input.is_none() { @@ -269,6 +267,7 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -278,8 +277,13 @@ where self.width, self.padding, self.size, + self.font, self.line_height, self.icon.as_ref(), + tree.state.downcast_ref::>(), + &self.value, + &self.placeholder, + self.is_secure, ) } @@ -290,7 +294,7 @@ where _renderer: &Renderer, operation: &mut dyn Operation, ) { - let state = tree.state.downcast_mut::(); + let state = tree.state.downcast_mut::>(); operation.focusable(state, self.id.as_ref().map(|id| &id.0)); operation.text_input(state, self.id.as_ref().map(|id| &id.0)); @@ -322,7 +326,7 @@ where self.on_input.as_deref(), self.on_paste.as_deref(), &self.on_submit, - || tree.state.downcast_mut::(), + || tree.state.downcast_mut::>(), ) } @@ -341,12 +345,8 @@ where theme, layout, cursor, - tree.state.downcast_ref::(), + tree.state.downcast_ref::>(), &self.value, - &self.placeholder, - self.size, - self.line_height, - self.font, self.on_input.is_none(), self.is_secure, self.icon.as_ref(), @@ -388,7 +388,7 @@ pub struct Icon { /// The unicode code point that will be used as the icon. pub code_point: char, /// The font size of the content. - pub size: Option, + pub size: Option, /// The spacing between the [`Icon`] and the text in a [`TextInput`]. pub spacing: f32, /// The side of a [`TextInput`] where to display the [`Icon`]. @@ -465,29 +465,65 @@ pub fn layout( limits: &layout::Limits, width: Length, padding: Padding, - size: Option, + size: Option, + font: Option, line_height: text::LineHeight, icon: Option<&Icon>, + state: &State, + value: &Value, + placeholder: &str, + is_secure: bool, ) -> layout::Node where Renderer: text::Renderer, { + let font = font.unwrap_or_else(|| renderer.default_font()); let text_size = size.unwrap_or_else(|| renderer.default_size()); + let padding = padding.fit(Size::ZERO, limits.max()); let limits = limits .width(width) .pad(padding) - .height(line_height.to_absolute(Pixels(text_size))); + .height(line_height.to_absolute(text_size)); let text_bounds = limits.resolve(Size::ZERO); - if let Some(icon) = icon { - let icon_width = renderer.measure_width( - &icon.code_point.to_string(), - icon.size.unwrap_or_else(|| renderer.default_size()), - icon.font, - text::Shaping::Advanced, + let placeholder_text = Text { + font, + line_height, + content: placeholder, + bounds: Size::new(f32::INFINITY, text_bounds.height), + size: text_size, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: text::Shaping::Advanced, + }; + + renderer.update_paragraph( + &mut state.placeholder_paragraph.borrow_mut(), + placeholder_text, + ); + + if is_secure { + renderer.update_paragraph( + &mut state.paragraph.borrow_mut(), + Text { + content: &value.secure().to_string(), + ..placeholder_text + }, + ); + } else { + renderer.update_paragraph( + &mut state.paragraph.borrow_mut(), + Text { + content: &value.to_string(), + ..placeholder_text + }, ); + } + + if let Some(icon) = icon { + let icon_width = 0.0; // TODO let mut text_node = layout::Node::new( text_bounds - Size::new(icon_width + icon.spacing, 0.0), @@ -537,19 +573,31 @@ pub fn update<'a, Message, Renderer>( clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, value: &mut Value, - size: Option, + size: Option, line_height: text::LineHeight, font: Option, is_secure: bool, on_input: Option<&dyn Fn(String) -> Message>, on_paste: Option<&dyn Fn(String) -> Message>, on_submit: &Option, - state: impl FnOnce() -> &'a mut State, + state: impl FnOnce() -> &'a mut State, ) -> event::Status where Message: Clone, Renderer: text::Renderer, { + let update_cache = |state, value| { + replace_paragraph( + renderer, + state, + layout, + value, + font, + size, + line_height, + ) + }; + match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { @@ -592,11 +640,7 @@ where }; find_cursor_position( - renderer, text_layout.bounds(), - font, - size, - line_height, &value, state, target, @@ -621,11 +665,7 @@ where state.cursor.select_all(value); } else { let position = find_cursor_position( - renderer, text_layout.bounds(), - font, - size, - line_height, value, state, target, @@ -671,11 +711,7 @@ where }; let position = find_cursor_position( - renderer, text_layout.bounds(), - font, - size, - line_height, &value, state, target, @@ -710,6 +746,8 @@ where focus.updated_at = Instant::now(); + update_cache(state, value); + return event::Status::Captured; } } @@ -749,6 +787,8 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + + update_cache(state, value); } keyboard::KeyCode::Delete => { if platform::is_jump_modifier_pressed(modifiers) @@ -769,6 +809,8 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + + update_cache(state, value); } keyboard::KeyCode::Left => { if platform::is_jump_modifier_pressed(modifiers) @@ -844,6 +886,8 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + + update_cache(state, value); } keyboard::KeyCode::V => { if state.keyboard_modifiers.command() @@ -876,6 +920,8 @@ where shell.publish(message); state.is_pasting = Some(content); + + update_cache(state, value); } else { state.is_pasting = None; } @@ -979,12 +1025,8 @@ pub fn draw( theme: &Renderer::Theme, layout: Layout<'_>, cursor: mouse::Cursor, - state: &State, + state: &State, value: &Value, - placeholder: &str, - size: Option, - line_height: text::LineHeight, - font: Option, is_disabled: bool, is_secure: bool, icon: Option<&Icon>, @@ -1023,28 +1065,14 @@ pub fn draw( appearance.background, ); - if let Some(icon) = icon { - let icon_layout = children_layout.next().unwrap(); + if let Some(_icon) = icon { + let _icon_layout = children_layout.next().unwrap(); - renderer.fill_text(Text { - content: &icon.code_point.to_string(), - size: icon.size.unwrap_or_else(|| renderer.default_size()), - line_height: text::LineHeight::default(), - font: icon.font, - color: appearance.icon_color, - bounds: Rectangle { - y: text_bounds.center_y(), - ..icon_layout.bounds() - }, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - shaping: text::Shaping::Advanced, - }); + // TODO } let text = value.to_string(); - let font = font.unwrap_or_else(|| renderer.default_font()); - let size = size.unwrap_or_else(|| renderer.default_size()); + let paragraph = &state.paragraph.borrow() as &Renderer::Paragraph; let (cursor, offset) = if let Some(focus) = state .is_focused @@ -1055,12 +1083,9 @@ pub fn draw( cursor::State::Index(position) => { let (text_value_width, offset) = measure_cursor_and_scroll_offset( - renderer, + paragraph, text_bounds, - value, - size, position, - font, ); let is_cursor_visible = ((focus.now - focus.updated_at) @@ -1096,22 +1121,16 @@ pub fn draw( let (left_position, left_offset) = measure_cursor_and_scroll_offset( - renderer, + paragraph, text_bounds, - value, - size, left, - font, ); let (right_position, right_offset) = measure_cursor_and_scroll_offset( - renderer, + paragraph, text_bounds, - value, - size, right, - font, ); let width = right_position - left_position; @@ -1143,12 +1162,7 @@ pub fn draw( (None, 0.0) }; - let text_width = renderer.measure_width( - if text.is_empty() { placeholder } else { &text }, - size, - font, - text::Shaping::Advanced, - ); + let text_width = paragraph.min_width(); let render = |renderer: &mut Renderer| { if let Some((cursor, color)) = cursor { @@ -1157,27 +1171,23 @@ pub fn draw( renderer.with_translation(Vector::ZERO, |_| {}); } - renderer.fill_text(Text { - content: if text.is_empty() { placeholder } else { &text }, - color: if text.is_empty() { + let placeholder_paragraph = state.placeholder_paragraph.borrow(); + + renderer.fill_paragraph( + if text.is_empty() { + &placeholder_paragraph + } else { + paragraph + }, + Point::new(text_bounds.x, text_bounds.center_y()), + if text.is_empty() { theme.placeholder_color(style) } else if is_disabled { theme.disabled_color(style) } else { theme.value_color(style) }, - font, - bounds: Rectangle { - y: text_bounds.center_y(), - width: f32::INFINITY, - ..text_bounds - }, - size, - line_height, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - shaping: text::Shaping::Advanced, - }); + ); }; if text_width > text_bounds.width { @@ -1208,7 +1218,9 @@ pub fn mouse_interaction( /// The state of a [`TextInput`]. #[derive(Debug, Default, Clone)] -pub struct State { +pub struct State { + paragraph: RefCell

, + placeholder_paragraph: RefCell

, is_focused: Option, is_dragging: bool, is_pasting: Option, @@ -1225,7 +1237,7 @@ struct Focus { is_window_focused: bool, } -impl State { +impl State

{ /// Creates a new [`State`], representing an unfocused [`TextInput`]. pub fn new() -> Self { Self::default() @@ -1234,6 +1246,8 @@ impl State { /// Creates a new [`State`], representing a focused [`TextInput`]. pub fn focused() -> Self { Self { + paragraph: RefCell::new(P::default()), + placeholder_paragraph: RefCell::new(P::default()), is_focused: None, is_dragging: false, is_pasting: None, @@ -1292,7 +1306,7 @@ impl State { } } -impl operation::Focusable for State { +impl operation::Focusable for State

{ fn is_focused(&self) -> bool { State::is_focused(self) } @@ -1306,7 +1320,7 @@ impl operation::Focusable for State { } } -impl operation::TextInput for State { +impl operation::TextInput for State

{ fn move_cursor_to_front(&mut self) { State::move_cursor_to_front(self) } @@ -1336,17 +1350,11 @@ mod platform { } } -fn offset( - renderer: &Renderer, +fn offset( text_bounds: Rectangle, - font: Renderer::Font, - size: f32, value: &Value, - state: &State, -) -> f32 -where - Renderer: text::Renderer, -{ + state: &State

, +) -> f32 { if state.is_focused() { let cursor = state.cursor(); @@ -1356,12 +1364,9 @@ where }; let (_, offset) = measure_cursor_and_scroll_offset( - renderer, + &state.paragraph.borrow() as &P, text_bounds, - value, - size, focus_position, - font, ); offset @@ -1370,63 +1375,35 @@ where } } -fn measure_cursor_and_scroll_offset( - renderer: &Renderer, +fn measure_cursor_and_scroll_offset( + paragraph: &impl text::Paragraph, text_bounds: Rectangle, - value: &Value, - size: f32, cursor_index: usize, - font: Renderer::Font, -) -> (f32, f32) -where - Renderer: text::Renderer, -{ - let text_before_cursor = value.until(cursor_index).to_string(); +) -> (f32, f32) { + let grapheme_position = paragraph + .grapheme_position(0, cursor_index) + .unwrap_or(Point::ORIGIN); - let text_value_width = renderer.measure_width( - &text_before_cursor, - size, - font, - text::Shaping::Advanced, - ); + let offset = ((grapheme_position.x + 5.0) - text_bounds.width).max(0.0); - let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0); - - (text_value_width, offset) + (grapheme_position.x, offset) } /// Computes the position of the text cursor at the given X coordinate of /// a [`TextInput`]. -fn find_cursor_position( - renderer: &Renderer, +fn find_cursor_position( text_bounds: Rectangle, - font: Option, - size: Option, - line_height: text::LineHeight, value: &Value, - state: &State, + state: &State

, x: f32, -) -> Option -where - Renderer: text::Renderer, -{ - let font = font.unwrap_or_else(|| renderer.default_font()); - let size = size.unwrap_or_else(|| renderer.default_size()); - - let offset = offset(renderer, text_bounds, font, size, value, state); +) -> Option { + let offset = offset(text_bounds, value, state); let value = value.to_string(); - let char_offset = renderer - .hit_test( - &value, - size, - line_height, - font, - Size::INFINITY, - text::Shaping::Advanced, - Point::new(x + offset, text_bounds.height / 2.0), - true, - ) + let char_offset = state + .paragraph + .borrow() + .hit_test(Point::new(x + offset, text_bounds.height / 2.0)) .map(text::Hit::cursor)?; Some( @@ -1438,4 +1415,33 @@ where ) } +fn replace_paragraph( + renderer: &Renderer, + state: &mut State, + layout: Layout<'_>, + value: &Value, + font: Option, + text_size: Option, + line_height: text::LineHeight, +) where + Renderer: text::Renderer, +{ + let font = font.unwrap_or_else(|| renderer.default_font()); + let text_size = text_size.unwrap_or_else(|| renderer.default_size()); + + let mut children_layout = layout.children(); + let text_bounds = children_layout.next().unwrap().bounds(); + + *state.paragraph.get_mut() = renderer.create_paragraph(Text { + font, + line_height, + content: &value.to_string(), + bounds: Size::new(f32::INFINITY, text_bounds.height), + size: text_size, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + shaping: text::Shaping::Advanced, + }); +} + const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500; diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index c8187181..4ddc8db6 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -6,12 +6,12 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::text; use crate::core::touch; -use crate::core::widget::Tree; +use crate::core::widget; +use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Alignment, Clipboard, Element, Event, Layout, Length, Pixels, Rectangle, - Shell, Widget, + Clipboard, Element, Event, Layout, Length, Pixels, Rectangle, Shell, Size, + Widget, }; -use crate::{Row, Text}; pub use crate::style::toggler::{Appearance, StyleSheet}; @@ -42,7 +42,7 @@ where label: Option, width: Length, size: f32, - text_size: Option, + text_size: Option, text_line_height: text::LineHeight, text_alignment: alignment::Horizontal, text_shaping: text::Shaping, @@ -105,7 +105,7 @@ where /// Sets the text size o the [`Toggler`]. pub fn text_size(mut self, text_size: impl Into) -> Self { - self.text_size = Some(text_size.into().0); + self.text_size = Some(text_size.into()); self } @@ -160,6 +160,14 @@ where Renderer: text::Renderer, Renderer::Theme: StyleSheet + crate::text::StyleSheet, { + fn tag(&self) -> tree::Tag { + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(widget::text::State::::default()) + } + fn width(&self) -> Length { self.width } @@ -170,32 +178,41 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let mut row = Row::<(), Renderer>::new() - .width(self.width) - .spacing(self.spacing) - .align_items(Alignment::Center); - - if let Some(label) = &self.label { - row = row.push( - Text::new(label) - .horizontal_alignment(self.text_alignment) - .font(self.font.unwrap_or_else(|| renderer.default_font())) - .width(self.width) - .size( - self.text_size - .unwrap_or_else(|| renderer.default_size()), + let limits = limits.width(self.width); + + layout::next_to_each_other( + &limits, + self.spacing, + |_| layout::Node::new(Size::new(2.0 * self.size, self.size)), + |limits| { + if let Some(label) = self.label.as_deref() { + let state = tree + .state + .downcast_ref::>(); + + widget::text::layout( + state, + renderer, + limits, + self.width, + Length::Shrink, + label, + self.text_line_height, + self.text_size, + self.font, + self.text_alignment, + alignment::Vertical::Top, + self.text_shaping, ) - .line_height(self.text_line_height) - .shaping(self.text_shaping), - ); - } - - row = row.push(Row::new().width(2.0 * self.size).height(self.size)); - - row.layout(renderer, limits) + } else { + layout::Node::new(Size::ZERO) + } + }, + ) } fn on_event( @@ -243,7 +260,7 @@ where fn draw( &self, - _state: &Tree, + tree: &Tree, renderer: &mut Renderer, theme: &Renderer::Theme, style: &renderer::Style, @@ -259,28 +276,21 @@ where const SPACE_RATIO: f32 = 0.05; let mut children = layout.children(); + let toggler_layout = children.next().unwrap(); - if let Some(label) = &self.label { + if self.label.is_some() { let label_layout = children.next().unwrap(); crate::text::draw( renderer, style, label_layout, - label, - self.text_size, - self.text_line_height, - self.font, + tree.state.downcast_ref(), Default::default(), - self.text_alignment, - alignment::Vertical::Center, - self.text_shaping, ); } - let toggler_layout = children.next().unwrap(); let bounds = toggler_layout.bounds(); - let is_mouse_over = cursor.is_over(layout.bounds()); let style = if is_mouse_over { diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index faa3f3e1..0444850e 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -107,11 +107,14 @@ where Renderer::Theme: container::StyleSheet + crate::text::StyleSheet, { fn children(&self) -> Vec { - vec![widget::Tree::new(&self.content)] + vec![ + widget::Tree::new(&self.content), + widget::Tree::new(&self.tooltip as &dyn Widget), + ] } fn diff(&self, tree: &mut widget::Tree) { - tree.diff_children(std::slice::from_ref(&self.content)) + tree.diff_children(&[self.content.as_widget(), &self.tooltip]) } fn state(&self) -> widget::tree::State { @@ -132,10 +135,11 @@ where fn layout( &self, + tree: &widget::Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.content.as_widget().layout(renderer, limits) + self.content.as_widget().layout(tree, renderer, limits) } fn on_event( @@ -214,8 +218,10 @@ where ) -> Option> { let state = tree.state.downcast_ref::(); + let mut children = tree.children.iter_mut(); + let content = self.content.as_widget_mut().overlay( - &mut tree.children[0], + children.next().unwrap(), layout, renderer, ); @@ -225,6 +231,7 @@ where layout.position(), Box::new(Overlay { tooltip: &self.tooltip, + state: children.next().unwrap(), cursor_position, content_bounds: layout.bounds(), snap_within_viewport: self.snap_within_viewport, @@ -295,6 +302,7 @@ where Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, { tooltip: &'b Text<'a, Renderer>, + state: &'b widget::Tree, cursor_position: Point, content_bounds: Rectangle, snap_within_viewport: bool, @@ -320,6 +328,7 @@ where let text_layout = Widget::<(), Renderer>::layout( self.tooltip, + self.state, renderer, &layout::Limits::new( Size::ZERO, diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index efca302a..a11fec75 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -166,6 +166,7 @@ where fn layout( &self, + _tree: &Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { -- cgit From 89acf0217e0acd92a82bff1fd516cd4266c0878a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 30 Aug 2023 05:06:08 +0200 Subject: Use `min_bounds` for cached text --- graphics/src/text/cache.rs | 17 +++++++++++++---- tiny_skia/src/text.rs | 8 ++++---- wgpu/src/text.rs | 17 ++++++++++------- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/graphics/src/text/cache.rs b/graphics/src/text/cache.rs index 8aea6715..9e4fbf65 100644 --- a/graphics/src/text/cache.rs +++ b/graphics/src/text/cache.rs @@ -8,7 +8,7 @@ use std::hash::{BuildHasher, Hash, Hasher}; #[allow(missing_debug_implementations)] #[derive(Default)] pub struct Cache { - entries: FxHashMap, + entries: FxHashMap, aliases: FxHashMap, recently_used: FxHashSet, hasher: HashBuilder, @@ -25,7 +25,7 @@ impl Cache { Self::default() } - pub fn get(&self, key: &KeyHash) -> Option<&cosmic_text::Buffer> { + pub fn get(&self, key: &KeyHash) -> Option<&Entry> { self.entries.get(key) } @@ -33,7 +33,7 @@ impl Cache { &mut self, font_system: &mut cosmic_text::FontSystem, key: Key<'_>, - ) -> (KeyHash, &mut cosmic_text::Buffer) { + ) -> (KeyHash, &mut Entry) { let hash = key.hash(self.hasher.build_hasher()); if let Some(hash) = self.aliases.get(&hash) { @@ -59,7 +59,10 @@ impl Cache { ); let bounds = text::measure(&buffer); - let _ = entry.insert(buffer); + let _ = entry.insert(Entry { + buffer, + min_bounds: bounds, + }); for bounds in [ bounds, @@ -118,3 +121,9 @@ impl Key<'_> { } pub type KeyHash = u64; + +#[allow(missing_debug_implementations)] +pub struct Entry { + pub buffer: cosmic_text::Buffer, + pub min_bounds: Size, +} diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index e4c5ad9b..6047a826 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -77,10 +77,10 @@ impl Pipeline { shaping, }; - let (_, buffer) = self.cache.get_mut().allocate(font_system, key); + let (_, entry) = self.cache.get_mut().allocate(font_system, key); - let max_width = bounds.width * scale_factor; - let total_height = bounds.height * scale_factor; + let max_width = entry.min_bounds.width * scale_factor; + let total_height = entry.min_bounds.height * scale_factor; let bounds = bounds * scale_factor; @@ -98,7 +98,7 @@ impl Pipeline { let mut swash = cosmic_text::SwashCache::new(); - for run in buffer.layout_runs() { + for run in entry.buffer.layout_runs() { for glyph in run.glyphs { let physical_glyph = glyph.physical((x, y), scale_factor); diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index da2062fe..ee352368 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -59,7 +59,7 @@ impl Pipeline { device: &wgpu::Device, queue: &wgpu::Queue, sections: &[Text<'_>], - bounds: Rectangle, + layer_bounds: Rectangle, scale_factor: f32, target_size: Size, ) { @@ -98,8 +98,8 @@ impl Pipeline { ), font: text.font, bounds: Size { - width: bounds.width, - height: bounds.height, + width: text.bounds.width, + height: text.bounds.height, }, shaping: text.shaping, }, @@ -110,7 +110,7 @@ impl Pipeline { }) .collect(); - let layer_bounds = bounds * scale_factor; + let layer_bounds = layer_bounds * scale_factor; let text_areas = sections.iter().zip(allocations.iter()).filter_map( |(section, allocation)| { @@ -144,11 +144,14 @@ impl Pipeline { return None; }; - let buffer = cache.get(key).expect("Get cached buffer"); + let entry = cache.get(key).expect("Get cached buffer"); ( - buffer, - text.bounds, + &entry.buffer, + Rectangle::new( + text.bounds.position(), + entry.min_bounds, + ), text.horizontal_alignment, text.vertical_alignment, text.color, -- cgit From c44611cc7d43b5d8336509030e1181d1ec04ff64 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 30 Aug 2023 05:33:39 +0200 Subject: Fix vertical alignment in `layout::next_to_each_other` --- core/src/layout.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/src/layout.rs b/core/src/layout.rs index 50ccf1f4..caf315b6 100644 --- a/core/src/layout.rs +++ b/core/src/layout.rs @@ -71,7 +71,7 @@ pub fn next_to_each_other( left: impl FnOnce(&Limits) -> Node, right: impl FnOnce(&Limits) -> Node, ) -> Node { - let left_node = left(limits); + let mut left_node = left(limits); let left_size = left_node.size(); let right_limits = limits.shrink(Size::new(left_size.width + spacing, 0.0)); @@ -79,7 +79,14 @@ pub fn next_to_each_other( let mut right_node = right(&right_limits); let right_size = right_node.size(); - right_node.move_to(Point::new(left_size.width + spacing, 0.0)); + let (left_y, right_y) = if left_size.height > right_size.height { + (0.0, (left_size.height - right_size.height) / 2.0) + } else { + ((right_size.height - left_size.height) / 2.0, 0.0) + }; + + left_node.move_to(Point::new(0.0, left_y)); + right_node.move_to(Point::new(left_size.width + spacing, right_y)); Node::with_children( Size::new( -- cgit From ffd0f4df4521a34edf7b1d3aa22cf47d0b64edfa Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 30 Aug 2023 05:34:01 +0200 Subject: Add some default spacing for `Toggler` --- widget/src/toggler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 4ddc8db6..89e6b5d3 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -85,7 +85,7 @@ where text_line_height: text::LineHeight::default(), text_alignment: alignment::Horizontal::Left, text_shaping: text::Shaping::Basic, - spacing: 0.0, + spacing: Self::DEFAULT_SIZE / 2.0, font: None, style: Default::default(), } -- cgit From 301e6e5fdc40f36f98bd8fef0fa923745533ad27 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 30 Aug 2023 05:34:17 +0200 Subject: Reduce default spacing of `Checkbox` --- widget/src/checkbox.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index a66ce3ff..0158f15c 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -61,7 +61,7 @@ where const DEFAULT_SIZE: f32 = 20.0; /// The default spacing of a [`Checkbox`]. - const DEFAULT_SPACING: f32 = 15.0; + const DEFAULT_SPACING: f32 = 10.0; /// Creates a new [`Checkbox`]. /// -- cgit From a026e917d3364e58fd827995261158d8cb356ce9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 30 Aug 2023 06:36:24 +0200 Subject: Make `widget::Tree` mutable in `Widget::layout` --- core/src/element.rs | 4 +-- core/src/layout/flex.rs | 4 +-- core/src/overlay.rs | 2 +- core/src/overlay/element.rs | 4 +-- core/src/overlay/group.rs | 4 +-- core/src/widget.rs | 2 +- core/src/widget/text.rs | 19 ++++++------- examples/custom_quad/src/main.rs | 2 +- examples/custom_widget/src/main.rs | 2 +- examples/geometry/src/main.rs | 2 +- examples/loading_spinners/src/circular.rs | 2 +- examples/loading_spinners/src/linear.rs | 2 +- examples/modal/src/main.rs | 12 ++++---- examples/toast/src/main.rs | 12 ++++---- runtime/src/user_interface.rs | 6 ++-- widget/src/button.rs | 10 ++++--- widget/src/canvas.rs | 2 +- widget/src/checkbox.rs | 4 +-- widget/src/column.rs | 4 +-- widget/src/combo_box.rs | 2 +- widget/src/container.rs | 2 +- widget/src/image.rs | 2 +- widget/src/image/viewer.rs | 2 +- widget/src/lazy.rs | 4 +-- widget/src/lazy/component.rs | 4 +-- widget/src/lazy/responsive.rs | 6 ++-- widget/src/mouse_area.rs | 2 +- widget/src/overlay/menu.rs | 4 +-- widget/src/pane_grid.rs | 13 ++++++--- widget/src/pane_grid/content.rs | 14 ++++++---- widget/src/pane_grid/title_bar.rs | 6 ++-- widget/src/pick_list.rs | 28 ++++++++----------- widget/src/progress_bar.rs | 2 +- widget/src/qr_code.rs | 2 +- widget/src/radio.rs | 4 +-- widget/src/row.rs | 4 +-- widget/src/rule.rs | 2 +- widget/src/scrollable.rs | 4 +-- widget/src/slider.rs | 2 +- widget/src/space.rs | 2 +- widget/src/svg.rs | 2 +- widget/src/text_input.rs | 46 +++++++++++++------------------ widget/src/toggler.rs | 4 +-- widget/src/tooltip.rs | 6 ++-- widget/src/vertical_slider.rs | 2 +- 45 files changed, 135 insertions(+), 135 deletions(-) diff --git a/core/src/element.rs b/core/src/element.rs index 0d23a9e7..02f16bcb 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -306,7 +306,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -492,7 +492,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/core/src/layout/flex.rs b/core/src/layout/flex.rs index 86b1a45b..c02b63d8 100644 --- a/core/src/layout/flex.rs +++ b/core/src/layout/flex.rs @@ -67,7 +67,7 @@ pub fn resolve( spacing: f32, align_items: Alignment, items: &[Element<'_, Message, Renderer>], - trees: &[widget::Tree], + trees: &mut [widget::Tree], ) -> Node where Renderer: crate::Renderer, @@ -83,7 +83,7 @@ where let mut nodes: Vec = Vec::with_capacity(items.len()); nodes.resize(items.len(), Node::default()); - for (i, (child, tree)) in items.iter().zip(trees).enumerate() { + for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() { let fill_factor = match axis { Axis::Horizontal => child.as_widget().width(), Axis::Vertical => child.as_widget().height(), diff --git a/core/src/overlay.rs b/core/src/overlay.rs index 2e05db93..f71f25f7 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -25,7 +25,7 @@ where /// /// [`Node`]: layout::Node fn layout( - &self, + &mut self, renderer: &Renderer, bounds: Size, position: Point, diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index 29b404b8..689e69be 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -54,7 +54,7 @@ where /// Computes the layout of the [`Element`] in the given bounds. pub fn layout( - &self, + &mut self, renderer: &Renderer, bounds: Size, translation: Vector, @@ -150,7 +150,7 @@ where Renderer: crate::Renderer, { fn layout( - &self, + &mut self, renderer: &Renderer, bounds: Size, position: Point, diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index 691686cd..a0bae6bb 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -61,7 +61,7 @@ where Renderer: crate::Renderer, { fn layout( - &self, + &mut self, renderer: &Renderer, bounds: Size, position: Point, @@ -71,7 +71,7 @@ where layout::Node::with_children( bounds, self.children - .iter() + .iter_mut() .map(|child| child.layout(renderer, bounds, translation)) .collect(), ) diff --git a/core/src/widget.rs b/core/src/widget.rs index 70328ff7..294d5984 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -55,7 +55,7 @@ where /// user interface. fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node; diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 0405537b..b349399b 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -3,12 +3,11 @@ use crate::alignment; use crate::layout; use crate::mouse; use crate::renderer; -use crate::text::{self, Paragraph as _}; +use crate::text::{self, Paragraph}; use crate::widget::tree::{self, Tree}; use crate::{Color, Element, Layout, Length, Pixels, Point, Rectangle, Widget}; use std::borrow::Cow; -use std::cell::RefCell; pub use text::{LineHeight, Shaping}; @@ -120,7 +119,7 @@ where /// The internal state of a [`Text`] widget. #[derive(Debug, Default)] -pub struct State(RefCell); +pub struct State(P); impl<'a, Message, Renderer> Widget for Text<'a, Renderer> where @@ -132,7 +131,7 @@ where } fn state(&self) -> tree::State { - tree::State::new(State(RefCell::new(Renderer::Paragraph::default()))) + tree::State::new(State(Renderer::Paragraph::default())) } fn width(&self) -> Length { @@ -145,12 +144,12 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { layout( - tree.state.downcast_ref::>(), + tree.state.downcast_mut::>(), renderer, limits, self.width, @@ -189,7 +188,7 @@ where /// Produces the [`layout::Node`] of a [`Text`] widget. pub fn layout( - state: &State, + state: &mut State, renderer: &Renderer, limits: &layout::Limits, width: Length, @@ -211,10 +210,10 @@ where let size = size.unwrap_or_else(|| renderer.default_size()); let font = font.unwrap_or_else(|| renderer.default_font()); - let mut paragraph = state.0.borrow_mut(); + let State(ref mut paragraph) = state; renderer.update_paragraph( - &mut paragraph, + paragraph, text::Text { content, bounds, @@ -251,7 +250,7 @@ pub fn draw( ) where Renderer: text::Renderer, { - let paragraph = state.0.borrow(); + let State(ref paragraph) = state; let bounds = layout.bounds(); let x = match paragraph.horizontal_alignment() { diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs index 91401f73..13b08250 100644 --- a/examples/custom_quad/src/main.rs +++ b/examples/custom_quad/src/main.rs @@ -36,7 +36,7 @@ mod quad { fn layout( &self, - _tree: &widget::Tree, + _tree: &mut widget::Tree, _renderer: &Renderer, _limits: &layout::Limits, ) -> layout::Node { diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index e0a23541..32a14cbe 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -43,7 +43,7 @@ mod circle { fn layout( &self, - _tree: &widget::Tree, + _tree: &mut widget::Tree, _renderer: &Renderer, _limits: &layout::Limits, ) -> layout::Node { diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index a0d505b8..8ab3b493 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -26,7 +26,7 @@ mod rainbow { fn layout( &self, - _tree: &widget::Tree, + _tree: &mut widget::Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index 6bcfd507..bf01c3b4 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -254,7 +254,7 @@ where fn layout( &self, - _tree: &Tree, + _tree: &mut Tree, _renderer: &iced::Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs index 3addd7bb..c5bb4791 100644 --- a/examples/loading_spinners/src/linear.rs +++ b/examples/loading_spinners/src/linear.rs @@ -175,7 +175,7 @@ where fn layout( &self, - _tree: &Tree, + _tree: &mut Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index 5d47f02c..aa9107d0 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -285,13 +285,15 @@ mod modal { fn layout( &self, - tree: &widget::Tree, + tree: &mut widget::Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.base - .as_widget() - .layout(&tree.children[0], renderer, limits) + self.base.as_widget().layout( + &mut tree.children[0], + renderer, + limits, + ) } fn on_event( @@ -402,7 +404,7 @@ mod modal { Message: Clone, { fn layout( - &self, + &mut self, renderer: &Renderer, _bounds: Size, position: Point, diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 01eea3cc..50fa885a 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -326,13 +326,15 @@ mod toast { fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.content - .as_widget() - .layout(&tree.children[0], renderer, limits) + self.content.as_widget().layout( + &mut tree.children[0], + renderer, + limits, + ) } fn tag(&self) -> widget::tree::Tag { @@ -503,7 +505,7 @@ mod toast { for Overlay<'a, 'b, Message> { fn layout( - &self, + &mut self, renderer: &Renderer, bounds: Size, position: Point, diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index a730102c..b684fbde 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -96,7 +96,7 @@ where state.diff(root.as_widget()); let base = root.as_widget().layout( - &state, + &mut state, renderer, &layout::Limits::new(Size::ZERO, bounds), ); @@ -230,7 +230,7 @@ where let _ = ManuallyDrop::into_inner(manual_overlay); self.base = self.root.as_widget().layout( - &self.state, + &mut self.state, renderer, &layout::Limits::new(Size::ZERO, self.bounds), ); @@ -330,7 +330,7 @@ where shell.revalidate_layout(|| { self.base = self.root.as_widget().layout( - &self.state, + &mut self.state, renderer, &layout::Limits::new(Size::ZERO, self.bounds), ); diff --git a/widget/src/button.rs b/widget/src/button.rs index 1788b6c4..8ca4a542 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -159,14 +159,16 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { layout(limits, self.width, self.height, self.padding, |limits| { - self.content - .as_widget() - .layout(&tree.children[0], renderer, limits) + self.content.as_widget().layout( + &mut tree.children[0], + renderer, + limits, + ) }) } diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index d749355b..390f4d92 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -129,7 +129,7 @@ where fn layout( &self, - _tree: &Tree, + _tree: &mut Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 0158f15c..2860d496 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -184,7 +184,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -195,7 +195,7 @@ where |limits| { let state = tree .state - .downcast_ref::>(); + .downcast_mut::>(); widget::text::layout( state, diff --git a/widget/src/column.rs b/widget/src/column.rs index 107c3475..f2347cc9 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -122,7 +122,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -139,7 +139,7 @@ where self.spacing, self.align_items, &self.children, - &tree.children, + &mut tree.children, ) } diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 8c20ae8e..650954ef 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -305,7 +305,7 @@ where fn layout( &self, - tree: &widget::Tree, + tree: &mut widget::Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/container.rs b/widget/src/container.rs index c16c1117..ee7a4965 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -162,7 +162,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/image.rs b/widget/src/image.rs index f73ee5d7..3c83c87b 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -167,7 +167,7 @@ where fn layout( &self, - _tree: &Tree, + _tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 1f52bf2f..724d121e 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -105,7 +105,7 @@ where fn layout( &self, - _tree: &Tree, + _tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 412254f5..d980a934 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -152,7 +152,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -327,7 +327,7 @@ where Renderer: core::Renderer, { fn layout( - &self, + &mut self, renderer: &Renderer, bounds: Size, position: Point, diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index 9b3b13b2..fba057ee 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -254,7 +254,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -567,7 +567,7 @@ where S: 'static + Default, { fn layout( - &self, + &mut self, renderer: &Renderer, bounds: Size, position: Point, diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index 5ab8ed1a..0b819455 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -60,7 +60,7 @@ impl<'a, Message, Renderer> Content<'a, Message, Renderer> where Renderer: core::Renderer, { - fn layout(&mut self, tree: &Tree, renderer: &Renderer) { + fn layout(&mut self, tree: &mut Tree, renderer: &Renderer) { if self.layout.is_none() { self.layout = Some(self.element.as_widget().layout( tree, @@ -144,7 +144,7 @@ where fn layout( &self, - _tree: &Tree, + _tree: &mut Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -363,7 +363,7 @@ where Renderer: core::Renderer, { fn layout( - &self, + &mut self, renderer: &Renderer, bounds: Size, position: Point, diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 95b45b02..65d44dd5 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -120,7 +120,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 71703e71..5a2e1bf0 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -232,7 +232,7 @@ where Renderer::Theme: StyleSheet + container::StyleSheet, { fn layout( - &self, + &mut self, renderer: &Renderer, bounds: Size, position: Point, @@ -352,7 +352,7 @@ where fn layout( &self, - _tree: &Tree, + _tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 366d9a66..6e2b39a4 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -275,7 +275,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -475,7 +475,7 @@ where /// Calculates the [`Layout`] of a [`PaneGrid`]. pub fn layout( - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, node: &Node, @@ -483,14 +483,19 @@ pub fn layout( height: Length, spacing: f32, contents: impl Iterator, - layout_content: impl Fn(T, &Tree, &Renderer, &layout::Limits) -> layout::Node, + layout_content: impl Fn( + T, + &mut Tree, + &Renderer, + &layout::Limits, + ) -> layout::Node, ) -> layout::Node { let limits = limits.width(width).height(height); let size = limits.resolve(Size::ZERO); let regions = node.pane_regions(spacing, size); let children = contents - .zip(tree.children.iter()) + .zip(tree.children.iter_mut()) .filter_map(|((pane, content), tree)| { let region = regions.get(&pane)?; let size = Size::new(region.width, region.height); diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index 8a74b4b9..5dbc5496 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -150,7 +150,7 @@ where pub(crate) fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -158,7 +158,7 @@ where let max_size = limits.max(); let title_bar_layout = title_bar.layout( - &tree.children[1], + &mut tree.children[1], renderer, &layout::Limits::new(Size::ZERO, max_size), ); @@ -166,7 +166,7 @@ where let title_bar_size = title_bar_layout.size(); let mut body_layout = self.body.as_widget().layout( - &tree.children[0], + &mut tree.children[0], renderer, &layout::Limits::new( Size::ZERO, @@ -184,9 +184,11 @@ where vec![title_bar_layout, body_layout], ) } else { - self.body - .as_widget() - .layout(&tree.children[0], renderer, limits) + self.body.as_widget().layout( + &mut tree.children[0], + renderer, + limits, + ) } } diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index c0fb9936..8a4523e8 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -213,7 +213,7 @@ where pub(crate) fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -221,7 +221,7 @@ where let max_size = limits.max(); let title_layout = self.content.as_widget().layout( - &tree.children[0], + &mut tree.children[0], renderer, &layout::Limits::new(Size::ZERO, max_size), ); @@ -230,7 +230,7 @@ where let mut node = if let Some(controls) = &self.controls { let mut controls_layout = controls.as_widget().layout( - &tree.children[1], + &mut tree.children[1], renderer, &layout::Limits::new(Size::ZERO, max_size), ); diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 719aa066..8feb1788 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -18,7 +18,6 @@ use crate::overlay::menu::{self, Menu}; use crate::scrollable; use std::borrow::Cow; -use std::cell::RefCell; pub use crate::style::pick_list::{Appearance, StyleSheet}; @@ -175,12 +174,12 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { layout( - tree.state.downcast_ref::>(), + tree.state.downcast_mut::>(), renderer, limits, self.width, @@ -305,8 +304,8 @@ pub struct State { keyboard_modifiers: keyboard::Modifiers, is_open: bool, hovered_option: Option, - option_paragraphs: RefCell>, - placeholder_paragraph: RefCell

, + options: Vec

, + placeholder: P, } impl State

{ @@ -317,8 +316,8 @@ impl State

{ keyboard_modifiers: keyboard::Modifiers::default(), is_open: bool::default(), hovered_option: Option::default(), - option_paragraphs: RefCell::new(Vec::new()), - placeholder_paragraph: RefCell::new(Default::default()), + options: Vec::new(), + placeholder: P::default(), } } } @@ -375,7 +374,7 @@ pub struct Icon { /// Computes the layout of a [`PickList`]. pub fn layout( - state: &State, + state: &mut State, renderer: &Renderer, limits: &layout::Limits, width: Length, @@ -397,9 +396,7 @@ where let font = font.unwrap_or_else(|| renderer.default_font()); let text_size = text_size.unwrap_or_else(|| renderer.default_size()); - let mut paragraphs = state.option_paragraphs.borrow_mut(); - - paragraphs.resize_with(options.len(), Default::default); + state.options.resize_with(options.len(), Default::default); let option_text = Text { content: "", @@ -415,7 +412,7 @@ where shaping: text_shaping, }; - for (option, paragraph) in options.iter().zip(paragraphs.iter_mut()) { + for (option, paragraph) in options.iter().zip(state.options.iter_mut()) { let label = option.to_string(); renderer.update_paragraph( @@ -428,9 +425,8 @@ where } if let Some(placeholder) = placeholder { - let mut paragraph = state.placeholder_paragraph.borrow_mut(); renderer.update_paragraph( - &mut paragraph, + &mut state.placeholder, Text { content: placeholder, ..option_text @@ -441,13 +437,13 @@ where let max_width = match width { Length::Shrink => { let labels_width = - paragraphs.iter().fold(0.0, |width, paragraph| { + state.options.iter().fold(0.0, |width, paragraph| { f32::max(width, paragraph.min_width()) }); labels_width.max( placeholder - .map(|_| state.placeholder_paragraph.borrow().min_width()) + .map(|_| state.placeholder.min_width()) .unwrap_or(0.0), ) } diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs index 8b1490af..07de72d5 100644 --- a/widget/src/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -95,7 +95,7 @@ where fn layout( &self, - _tree: &Tree, + _tree: &mut Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index 589704a5..d176021d 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -60,7 +60,7 @@ impl<'a, Message, Theme> Widget> for QRCode<'a> { fn layout( &self, - _tree: &Tree, + _tree: &mut Tree, _renderer: &Renderer, _limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/radio.rs b/widget/src/radio.rs index cb908ec4..a85dad63 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -211,7 +211,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -222,7 +222,7 @@ where |limits| { let state = tree .state - .downcast_ref::>(); + .downcast_mut::>(); widget::text::layout( state, diff --git a/widget/src/row.rs b/widget/src/row.rs index 17c49e67..71cf0509 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -114,7 +114,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -128,7 +128,7 @@ where self.spacing, self.align_items, &self.children, - &tree.children, + &mut tree.children, ) } diff --git a/widget/src/rule.rs b/widget/src/rule.rs index 032ff860..b5c5fa55 100644 --- a/widget/src/rule.rs +++ b/widget/src/rule.rs @@ -72,7 +72,7 @@ where fn layout( &self, - _tree: &Tree, + _tree: &mut Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index ce96883d..5decfd9d 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -230,7 +230,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -242,7 +242,7 @@ where &self.direction, |renderer, limits| { self.content.as_widget().layout( - &tree.children[0], + &mut tree.children[0], renderer, limits, ) diff --git a/widget/src/slider.rs b/widget/src/slider.rs index b4c9198a..a9727aa3 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -169,7 +169,7 @@ where fn layout( &self, - _tree: &Tree, + _tree: &mut Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/space.rs b/widget/src/space.rs index 84331870..e5a8f169 100644 --- a/widget/src/space.rs +++ b/widget/src/space.rs @@ -55,7 +55,7 @@ where fn layout( &self, - _tree: &Tree, + _tree: &mut Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/svg.rs b/widget/src/svg.rs index f61a4ce2..2d01d1ab 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -106,7 +106,7 @@ where fn layout( &self, - _tree: &Tree, + _tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 209ef968..75800a34 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -30,8 +30,6 @@ use crate::core::{ }; use crate::runtime::Command; -use std::cell::RefCell; - pub use iced_style::text_input::{Appearance, StyleSheet}; /// A field that can be filled with text. @@ -267,7 +265,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -280,7 +278,7 @@ where self.font, self.line_height, self.icon.as_ref(), - tree.state.downcast_ref::>(), + tree.state.downcast_mut::>(), &self.value, &self.placeholder, self.is_secure, @@ -469,7 +467,7 @@ pub fn layout( font: Option, line_height: text::LineHeight, icon: Option<&Icon>, - state: &State, + state: &mut State, value: &Value, placeholder: &str, is_secure: bool, @@ -499,14 +497,12 @@ where shaping: text::Shaping::Advanced, }; - renderer.update_paragraph( - &mut state.placeholder_paragraph.borrow_mut(), - placeholder_text, - ); + renderer + .update_paragraph(&mut state.placeholder_paragraph, placeholder_text); if is_secure { renderer.update_paragraph( - &mut state.paragraph.borrow_mut(), + &mut state.paragraph, Text { content: &value.secure().to_string(), ..placeholder_text @@ -514,7 +510,7 @@ where ); } else { renderer.update_paragraph( - &mut state.paragraph.borrow_mut(), + &mut state.paragraph, Text { content: &value.to_string(), ..placeholder_text @@ -1072,7 +1068,6 @@ pub fn draw( } let text = value.to_string(); - let paragraph = &state.paragraph.borrow() as &Renderer::Paragraph; let (cursor, offset) = if let Some(focus) = state .is_focused @@ -1083,7 +1078,7 @@ pub fn draw( cursor::State::Index(position) => { let (text_value_width, offset) = measure_cursor_and_scroll_offset( - paragraph, + &state.paragraph, text_bounds, position, ); @@ -1121,14 +1116,14 @@ pub fn draw( let (left_position, left_offset) = measure_cursor_and_scroll_offset( - paragraph, + &state.paragraph, text_bounds, left, ); let (right_position, right_offset) = measure_cursor_and_scroll_offset( - paragraph, + &state.paragraph, text_bounds, right, ); @@ -1162,7 +1157,7 @@ pub fn draw( (None, 0.0) }; - let text_width = paragraph.min_width(); + let text_width = state.paragraph.min_width(); let render = |renderer: &mut Renderer| { if let Some((cursor, color)) = cursor { @@ -1171,13 +1166,11 @@ pub fn draw( renderer.with_translation(Vector::ZERO, |_| {}); } - let placeholder_paragraph = state.placeholder_paragraph.borrow(); - renderer.fill_paragraph( if text.is_empty() { - &placeholder_paragraph + &state.placeholder_paragraph } else { - paragraph + &state.paragraph }, Point::new(text_bounds.x, text_bounds.center_y()), if text.is_empty() { @@ -1219,8 +1212,8 @@ pub fn mouse_interaction( /// The state of a [`TextInput`]. #[derive(Debug, Default, Clone)] pub struct State { - paragraph: RefCell

, - placeholder_paragraph: RefCell

, + paragraph: P, + placeholder_paragraph: P, is_focused: Option, is_dragging: bool, is_pasting: Option, @@ -1246,8 +1239,8 @@ impl State

{ /// Creates a new [`State`], representing a focused [`TextInput`]. pub fn focused() -> Self { Self { - paragraph: RefCell::new(P::default()), - placeholder_paragraph: RefCell::new(P::default()), + paragraph: P::default(), + placeholder_paragraph: P::default(), is_focused: None, is_dragging: false, is_pasting: None, @@ -1364,7 +1357,7 @@ fn offset( }; let (_, offset) = measure_cursor_and_scroll_offset( - &state.paragraph.borrow() as &P, + &state.paragraph, text_bounds, focus_position, ); @@ -1402,7 +1395,6 @@ fn find_cursor_position( let char_offset = state .paragraph - .borrow() .hit_test(Point::new(x + offset, text_bounds.height / 2.0)) .map(text::Hit::cursor)?; @@ -1432,7 +1424,7 @@ fn replace_paragraph( let mut children_layout = layout.children(); let text_bounds = children_layout.next().unwrap().bounds(); - *state.paragraph.get_mut() = renderer.create_paragraph(Text { + state.paragraph = renderer.create_paragraph(Text { font, line_height, content: &value.to_string(), diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 89e6b5d3..acb9b25d 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -178,7 +178,7 @@ where fn layout( &self, - tree: &Tree, + tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -192,7 +192,7 @@ where if let Some(label) = self.label.as_deref() { let state = tree .state - .downcast_ref::>(); + .downcast_mut::>(); widget::text::layout( state, diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 0444850e..534d901a 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -135,7 +135,7 @@ where fn layout( &self, - tree: &widget::Tree, + tree: &mut widget::Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { @@ -302,7 +302,7 @@ where Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, { tooltip: &'b Text<'a, Renderer>, - state: &'b widget::Tree, + state: &'b mut widget::Tree, cursor_position: Point, content_bounds: Rectangle, snap_within_viewport: bool, @@ -319,7 +319,7 @@ where Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, { fn layout( - &self, + &mut self, renderer: &Renderer, bounds: Size, position: Point, diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index a11fec75..1efcd63b 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -166,7 +166,7 @@ where fn layout( &self, - _tree: &Tree, + _tree: &mut Tree, _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { -- cgit From bcd9fdb521ce4cf21e9d1ee28157ed068ae1428c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 30 Aug 2023 06:43:45 +0200 Subject: Simplify new logic in `TextInput` --- widget/src/text_input.rs | 58 +++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 75800a34..f36b5616 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -497,26 +497,18 @@ where shaping: text::Shaping::Advanced, }; - renderer - .update_paragraph(&mut state.placeholder_paragraph, placeholder_text); - - if is_secure { - renderer.update_paragraph( - &mut state.paragraph, - Text { - content: &value.secure().to_string(), - ..placeholder_text - }, - ); - } else { - renderer.update_paragraph( - &mut state.paragraph, - Text { - content: &value.to_string(), - ..placeholder_text - }, - ); - } + renderer.update_paragraph(&mut state.placeholder, placeholder_text); + + let secure_value = is_secure.then(|| value.secure()); + let value = secure_value.as_ref().unwrap_or(value); + + renderer.update_paragraph( + &mut state.value, + Text { + content: &value.to_string(), + ..placeholder_text + }, + ); if let Some(icon) = icon { let icon_width = 0.0; // TODO @@ -1078,7 +1070,7 @@ pub fn draw( cursor::State::Index(position) => { let (text_value_width, offset) = measure_cursor_and_scroll_offset( - &state.paragraph, + &state.value, text_bounds, position, ); @@ -1116,14 +1108,14 @@ pub fn draw( let (left_position, left_offset) = measure_cursor_and_scroll_offset( - &state.paragraph, + &state.value, text_bounds, left, ); let (right_position, right_offset) = measure_cursor_and_scroll_offset( - &state.paragraph, + &state.value, text_bounds, right, ); @@ -1157,7 +1149,7 @@ pub fn draw( (None, 0.0) }; - let text_width = state.paragraph.min_width(); + let text_width = state.value.min_width(); let render = |renderer: &mut Renderer| { if let Some((cursor, color)) = cursor { @@ -1168,9 +1160,9 @@ pub fn draw( renderer.fill_paragraph( if text.is_empty() { - &state.placeholder_paragraph + &state.placeholder } else { - &state.paragraph + &state.value }, Point::new(text_bounds.x, text_bounds.center_y()), if text.is_empty() { @@ -1212,8 +1204,8 @@ pub fn mouse_interaction( /// The state of a [`TextInput`]. #[derive(Debug, Default, Clone)] pub struct State { - paragraph: P, - placeholder_paragraph: P, + value: P, + placeholder: P, is_focused: Option, is_dragging: bool, is_pasting: Option, @@ -1239,8 +1231,8 @@ impl State

{ /// Creates a new [`State`], representing a focused [`TextInput`]. pub fn focused() -> Self { Self { - paragraph: P::default(), - placeholder_paragraph: P::default(), + value: P::default(), + placeholder: P::default(), is_focused: None, is_dragging: false, is_pasting: None, @@ -1357,7 +1349,7 @@ fn offset( }; let (_, offset) = measure_cursor_and_scroll_offset( - &state.paragraph, + &state.value, text_bounds, focus_position, ); @@ -1394,7 +1386,7 @@ fn find_cursor_position( let value = value.to_string(); let char_offset = state - .paragraph + .value .hit_test(Point::new(x + offset, text_bounds.height / 2.0)) .map(text::Hit::cursor)?; @@ -1424,7 +1416,7 @@ fn replace_paragraph( let mut children_layout = layout.children(); let text_bounds = children_layout.next().unwrap().bounds(); - state.paragraph = renderer.create_paragraph(Text { + state.value = renderer.create_paragraph(Text { font, line_height, content: &value.to_string(), -- cgit From b51ffe53ed540d27022157dfb202364825806699 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 30 Aug 2023 06:44:40 +0200 Subject: Fix unnecessary dereference in `widget::text` --- core/src/widget/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index b349399b..53ed463e 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -266,7 +266,7 @@ pub fn draw( }; renderer.fill_paragraph( - ¶graph, + paragraph, Point::new(x, y), appearance.color.unwrap_or(style.text_color), ); -- cgit From 252a05c7f4013991f94ab1b60f138a4c49fdcbfb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 30 Aug 2023 07:26:48 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69b26eb..d80dc5e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Explicit text caching. [#2058](https://github.com/iced-rs/iced/pull/2058) + +Many thanks to... + +- Add your name here + ## [0.10.0] - 2023-07-28 ### Added - Text shaping, font fallback, and `iced_wgpu` overhaul. [#1697](https://github.com/iced-rs/iced/pull/1697) -- cgit From ce22d661fa2f4b50d2e7d9ba6bd07e625ec6789e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 1 Sep 2023 03:25:29 +0200 Subject: Remove `Clone` implementation for `Paragraph` --- graphics/src/text/paragraph.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index 7b70376a..f04b1e69 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -7,7 +7,7 @@ use crate::text::{self, FontSystem}; use std::fmt; use std::sync::{self, Arc}; -#[derive(Clone, PartialEq, Default)] +#[derive(PartialEq, Default)] pub struct Paragraph(Arc); struct Internal { -- cgit From 935c722a278071d3bcd0bf499a0ef8f687ed51ad Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 1 Sep 2023 03:59:57 +0200 Subject: Use `Arc::try_unwrap` in `Paragraph` We use `MaybeUninit` here instead of `Option` to save some cycles, but I will most likely change it for an `Option` since unsafe code is quite scary. --- graphics/src/text/paragraph.rs | 154 +++++++++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 59 deletions(-) diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index f04b1e69..c6921005 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -5,10 +5,10 @@ use crate::core::{Font, Pixels, Point, Size}; use crate::text::{self, FontSystem}; use std::fmt; +use std::mem::MaybeUninit; use std::sync::{self, Arc}; -#[derive(PartialEq, Default)] -pub struct Paragraph(Arc); +pub struct Paragraph(MaybeUninit>); struct Internal { buffer: cosmic_text::Buffer, @@ -21,6 +21,12 @@ struct Internal { min_bounds: Size, } +impl Default for Paragraph { + fn default() -> Self { + Self(MaybeUninit::new(Arc::new(Internal::default()))) + } +} + impl Paragraph { pub fn new() -> Self { Self::default() @@ -52,7 +58,7 @@ impl Paragraph { let min_bounds = text::measure(&buffer); - Self(Arc::new(Internal { + Self(MaybeUninit::new(Arc::new(Internal { buffer, content: text.content.to_owned(), font: text.font, @@ -61,54 +67,80 @@ impl Paragraph { shaping: text.shaping, bounds: text.bounds, min_bounds, - })) + }))) } pub fn buffer(&self) -> &cosmic_text::Buffer { - &self.0.buffer + #[allow(unsafe_code)] + &unsafe { self.0.assume_init_ref() }.buffer } pub fn downgrade(&self) -> Weak { + #[allow(unsafe_code)] + let paragraph = unsafe { self.0.assume_init_ref() }; + Weak { - raw: Arc::downgrade(&self.0), - min_bounds: self.0.min_bounds, - horizontal_alignment: self.0.horizontal_alignment, - vertical_alignment: self.0.vertical_alignment, + raw: Arc::downgrade(paragraph), + min_bounds: paragraph.min_bounds, + horizontal_alignment: paragraph.horizontal_alignment, + vertical_alignment: paragraph.vertical_alignment, } } pub fn resize(&mut self, new_bounds: Size, font_system: &FontSystem) { - if let Some(internal) = Arc::get_mut(&mut self.0) { - // If there is no strong reference holding on to the paragraph, we - // resize the buffer in-place - internal.buffer.set_size( - &mut font_system.write(), - new_bounds.width, - new_bounds.height, - ); - - internal.bounds = new_bounds; - internal.min_bounds = text::measure(&internal.buffer); - } else { - let metrics = self.0.buffer.metrics(); - - // If there is a strong reference somewhere, we recompute the buffer - // from scratch - *self = Self::with_text( - Text { - content: &self.0.content, - bounds: self.0.bounds, - size: Pixels(metrics.font_size), - line_height: LineHeight::Absolute(Pixels( - metrics.line_height, - )), - font: self.0.font, - horizontal_alignment: self.0.horizontal_alignment, - vertical_alignment: self.0.vertical_alignment, - shaping: self.0.shaping, - }, - font_system, - ); + // Place uninit for now, we always write to `self.0` in the end + let paragraph = std::mem::replace(&mut self.0, MaybeUninit::uninit()); + + // Mutable self guarantees unique access and `uninit` call only happens + // in this method. + #[allow(unsafe_code)] + let paragraph = unsafe { paragraph.assume_init() }; + + match Arc::try_unwrap(paragraph) { + Ok(mut internal) => { + internal.buffer.set_size( + &mut font_system.write(), + new_bounds.width, + new_bounds.height, + ); + + internal.bounds = new_bounds; + internal.min_bounds = text::measure(&internal.buffer); + + let _ = self.0.write(Arc::new(internal)); + } + Err(internal) => { + let metrics = internal.buffer.metrics(); + + // If there is a strong reference somewhere, we recompute the + // buffer from scratch + let new_paragraph = Self::with_text( + Text { + content: &internal.content, + bounds: internal.bounds, + size: Pixels(metrics.font_size), + line_height: LineHeight::Absolute(Pixels( + metrics.line_height, + )), + font: internal.font, + horizontal_alignment: internal.horizontal_alignment, + vertical_alignment: internal.vertical_alignment, + shaping: internal.shaping, + }, + font_system, + ); + + // New paragraph should always be initialized + #[allow(unsafe_code)] + let _ = self.0.write(unsafe { new_paragraph.0.assume_init() }); + } + } + } + + fn internal_ref(&self) -> &Internal { + #[allow(unsafe_code)] + unsafe { + self.0.assume_init_ref() } } } @@ -117,49 +149,51 @@ impl core::text::Paragraph for Paragraph { type Font = Font; fn content(&self) -> &str { - &self.0.content + &self.internal_ref().content } fn text_size(&self) -> Pixels { - Pixels(self.0.buffer.metrics().font_size) + Pixels(self.internal_ref().buffer.metrics().font_size) } fn line_height(&self) -> LineHeight { - LineHeight::Absolute(Pixels(self.0.buffer.metrics().line_height)) + LineHeight::Absolute(Pixels( + self.internal_ref().buffer.metrics().line_height, + )) } fn font(&self) -> Font { - self.0.font + self.internal_ref().font } fn shaping(&self) -> Shaping { - self.0.shaping + self.internal_ref().shaping } fn horizontal_alignment(&self) -> alignment::Horizontal { - self.0.horizontal_alignment + self.internal_ref().horizontal_alignment } fn vertical_alignment(&self) -> alignment::Vertical { - self.0.vertical_alignment + self.internal_ref().vertical_alignment } fn bounds(&self) -> Size { - self.0.bounds + self.internal_ref().bounds } fn min_bounds(&self) -> Size { - self.0.min_bounds + self.internal_ref().min_bounds } fn hit_test(&self, point: Point) -> Option { - let cursor = self.0.buffer.hit(point.x, point.y)?; + let cursor = self.internal_ref().buffer.hit(point.x, point.y)?; Some(Hit::CharOffset(cursor.index)) } fn grapheme_position(&self, line: usize, index: usize) -> Option { - let run = self.0.buffer.layout_runs().nth(line)?; + let run = self.internal_ref().buffer.layout_runs().nth(line)?; // TODO: Index represents a grapheme, not a glyph let glyph = run.glyphs.get(index).or_else(|| run.glyphs.last())?; @@ -210,14 +244,16 @@ impl Default for Internal { impl fmt::Debug for Paragraph { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let paragraph = self.internal_ref(); + f.debug_struct("Paragraph") - .field("content", &self.0.content) - .field("font", &self.0.font) - .field("shaping", &self.0.shaping) - .field("horizontal_alignment", &self.0.horizontal_alignment) - .field("vertical_alignment", &self.0.vertical_alignment) - .field("bounds", &self.0.bounds) - .field("min_bounds", &self.0.min_bounds) + .field("content", ¶graph.content) + .field("font", ¶graph.font) + .field("shaping", ¶graph.shaping) + .field("horizontal_alignment", ¶graph.horizontal_alignment) + .field("vertical_alignment", ¶graph.vertical_alignment) + .field("bounds", ¶graph.bounds) + .field("min_bounds", ¶graph.min_bounds) .finish() } } @@ -232,7 +268,7 @@ pub struct Weak { impl Weak { pub fn upgrade(&self) -> Option { - self.raw.upgrade().map(Paragraph) + self.raw.upgrade().map(MaybeUninit::new).map(Paragraph) } } -- cgit From 51e69d7040c943aad8453bc03031c75b4cb302bb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 1 Sep 2023 04:04:15 +0200 Subject: Replace `MaybeUninit` with `Option` in `paragraph` --- graphics/src/text/paragraph.rs | 72 ++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index c6921005..8e8907f9 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -5,10 +5,10 @@ use crate::core::{Font, Pixels, Point, Size}; use crate::text::{self, FontSystem}; use std::fmt; -use std::mem::MaybeUninit; use std::sync::{self, Arc}; -pub struct Paragraph(MaybeUninit>); +#[derive(Clone, PartialEq, Default)] +pub struct Paragraph(Option>); struct Internal { buffer: cosmic_text::Buffer, @@ -21,12 +21,6 @@ struct Internal { min_bounds: Size, } -impl Default for Paragraph { - fn default() -> Self { - Self(MaybeUninit::new(Arc::new(Internal::default()))) - } -} - impl Paragraph { pub fn new() -> Self { Self::default() @@ -58,7 +52,7 @@ impl Paragraph { let min_bounds = text::measure(&buffer); - Self(MaybeUninit::new(Arc::new(Internal { + Self(Some(Arc::new(Internal { buffer, content: text.content.to_owned(), font: text.font, @@ -71,13 +65,11 @@ impl Paragraph { } pub fn buffer(&self) -> &cosmic_text::Buffer { - #[allow(unsafe_code)] - &unsafe { self.0.assume_init_ref() }.buffer + &self.internal().buffer } pub fn downgrade(&self) -> Weak { - #[allow(unsafe_code)] - let paragraph = unsafe { self.0.assume_init_ref() }; + let paragraph = self.internal(); Weak { raw: Arc::downgrade(paragraph), @@ -88,13 +80,10 @@ impl Paragraph { } pub fn resize(&mut self, new_bounds: Size, font_system: &FontSystem) { - // Place uninit for now, we always write to `self.0` in the end - let paragraph = std::mem::replace(&mut self.0, MaybeUninit::uninit()); - - // Mutable self guarantees unique access and `uninit` call only happens - // in this method. - #[allow(unsafe_code)] - let paragraph = unsafe { paragraph.assume_init() }; + let paragraph = self + .0 + .take() + .expect("paragraph should always be initialized"); match Arc::try_unwrap(paragraph) { Ok(mut internal) => { @@ -107,14 +96,14 @@ impl Paragraph { internal.bounds = new_bounds; internal.min_bounds = text::measure(&internal.buffer); - let _ = self.0.write(Arc::new(internal)); + self.0 = Some(Arc::new(internal)); } Err(internal) => { let metrics = internal.buffer.metrics(); // If there is a strong reference somewhere, we recompute the // buffer from scratch - let new_paragraph = Self::with_text( + *self = Self::with_text( Text { content: &internal.content, bounds: internal.bounds, @@ -129,19 +118,14 @@ impl Paragraph { }, font_system, ); - - // New paragraph should always be initialized - #[allow(unsafe_code)] - let _ = self.0.write(unsafe { new_paragraph.0.assume_init() }); } } } - fn internal_ref(&self) -> &Internal { - #[allow(unsafe_code)] - unsafe { - self.0.assume_init_ref() - } + fn internal(&self) -> &Arc { + self.0 + .as_ref() + .expect("paragraph should always be initialized") } } @@ -149,51 +133,51 @@ impl core::text::Paragraph for Paragraph { type Font = Font; fn content(&self) -> &str { - &self.internal_ref().content + &self.internal().content } fn text_size(&self) -> Pixels { - Pixels(self.internal_ref().buffer.metrics().font_size) + Pixels(self.internal().buffer.metrics().font_size) } fn line_height(&self) -> LineHeight { LineHeight::Absolute(Pixels( - self.internal_ref().buffer.metrics().line_height, + self.internal().buffer.metrics().line_height, )) } fn font(&self) -> Font { - self.internal_ref().font + self.internal().font } fn shaping(&self) -> Shaping { - self.internal_ref().shaping + self.internal().shaping } fn horizontal_alignment(&self) -> alignment::Horizontal { - self.internal_ref().horizontal_alignment + self.internal().horizontal_alignment } fn vertical_alignment(&self) -> alignment::Vertical { - self.internal_ref().vertical_alignment + self.internal().vertical_alignment } fn bounds(&self) -> Size { - self.internal_ref().bounds + self.internal().bounds } fn min_bounds(&self) -> Size { - self.internal_ref().min_bounds + self.internal().min_bounds } fn hit_test(&self, point: Point) -> Option { - let cursor = self.internal_ref().buffer.hit(point.x, point.y)?; + let cursor = self.internal().buffer.hit(point.x, point.y)?; Some(Hit::CharOffset(cursor.index)) } fn grapheme_position(&self, line: usize, index: usize) -> Option { - let run = self.internal_ref().buffer.layout_runs().nth(line)?; + let run = self.internal().buffer.layout_runs().nth(line)?; // TODO: Index represents a grapheme, not a glyph let glyph = run.glyphs.get(index).or_else(|| run.glyphs.last())?; @@ -244,7 +228,7 @@ impl Default for Internal { impl fmt::Debug for Paragraph { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let paragraph = self.internal_ref(); + let paragraph = self.internal(); f.debug_struct("Paragraph") .field("content", ¶graph.content) @@ -268,7 +252,7 @@ pub struct Weak { impl Weak { pub fn upgrade(&self) -> Option { - self.raw.upgrade().map(MaybeUninit::new).map(Paragraph) + self.raw.upgrade().map(Some).map(Paragraph) } } -- cgit From 6758de2b4348d8990205721027524cf87a9db128 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 1 Sep 2023 04:14:06 +0200 Subject: Fix `Default` implementation for `Paragraph` --- graphics/src/text/paragraph.rs | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index 8e8907f9..d99b8412 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -7,7 +7,7 @@ use crate::text::{self, FontSystem}; use std::fmt; use std::sync::{self, Arc}; -#[derive(Clone, PartialEq, Default)] +#[derive(Clone, PartialEq)] pub struct Paragraph(Option>); struct Internal { @@ -195,6 +195,28 @@ impl core::text::Paragraph for Paragraph { } } +impl Default for Paragraph { + fn default() -> Self { + Self(Some(Arc::new(Internal::default()))) + } +} + +impl fmt::Debug for Paragraph { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let paragraph = self.internal(); + + f.debug_struct("Paragraph") + .field("content", ¶graph.content) + .field("font", ¶graph.font) + .field("shaping", ¶graph.shaping) + .field("horizontal_alignment", ¶graph.horizontal_alignment) + .field("vertical_alignment", ¶graph.vertical_alignment) + .field("bounds", ¶graph.bounds) + .field("min_bounds", ¶graph.min_bounds) + .finish() + } +} + impl PartialEq for Internal { fn eq(&self, other: &Self) -> bool { self.content == other.content @@ -226,22 +248,6 @@ impl Default for Internal { } } -impl fmt::Debug for Paragraph { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let paragraph = self.internal(); - - f.debug_struct("Paragraph") - .field("content", ¶graph.content) - .field("font", ¶graph.font) - .field("shaping", ¶graph.shaping) - .field("horizontal_alignment", ¶graph.horizontal_alignment) - .field("vertical_alignment", ¶graph.vertical_alignment) - .field("bounds", ¶graph.bounds) - .field("min_bounds", ¶graph.min_bounds) - .finish() - } -} - #[derive(Debug, Clone)] pub struct Weak { raw: sync::Weak, -- cgit From 548b9d0294cb18a79e1adb9629912e16a4fc353e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 1 Sep 2023 04:15:09 +0200 Subject: Fix `Widget::layout` for `Component` --- widget/src/lazy/component.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index fba057ee..fe99a7f2 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -258,8 +258,14 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { + let t = tree.state.downcast_mut::>>>(); + self.with_element(|element| { - element.as_widget().layout(tree, renderer, limits) + element.as_widget().layout( + &mut t.borrow_mut().as_mut().unwrap().children[0], + renderer, + limits, + ) }) } -- cgit From 601e5563d101788cbaa6febbe15cdf52e9f3ae9b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 1 Sep 2023 04:15:59 +0200 Subject: Fix `Widget::layout` for `Lazy` --- widget/src/lazy.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index d980a934..bf695a57 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -157,7 +157,9 @@ where limits: &layout::Limits, ) -> layout::Node { self.with_element(|element| { - element.as_widget().layout(tree, renderer, limits) + element + .as_widget() + .layout(&mut tree.children[0], renderer, limits) }) } -- cgit From 6cc354fdc4eec6576e0591cd3a2ce37155cbfa09 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Sep 2023 00:21:04 +0200 Subject: Update `wgpu` to `0.17` --- examples/integration/src/main.rs | 1 - wgpu/Cargo.toml | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 342d4c69..98b58f16 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -82,7 +82,6 @@ pub fn main() -> Result<(), Box> { futures::futures::executor::block_on(async { let adapter = wgpu::util::initialize_adapter_from_env_or_default( &instance, - backend, Some(&surface), ) .await diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 69568099..2fade559 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -14,8 +14,8 @@ svg = ["resvg"] web-colors = ["iced_graphics/web-colors"] [dependencies] -wgpu = "0.16" -glyphon = "0.3" +wgpu = "0.17" +glyphon = { git = "https://github.com/grovesNL/glyphon.git", rev = "20f0f8fa80e0d0df4c63634ce9176fa489546ca9" } raw-window-handle = "0.5" guillotiere = "0.6" futures = "0.3" -- cgit From d4f5dd7c95e5a17df08ef2454954fb9c0e05a834 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Sep 2023 00:24:09 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69b26eb..9d02ef4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- Updated `wgpu` to `0.17`. [#2065](https://github.com/iced-rs/iced/pull/2065) + +Many thanks to... + +- Add your here ## [0.10.0] - 2023-07-28 ### Added -- cgit From d518e7d423f7069ed72d84e39a08e69ac07680fa Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Sep 2023 00:28:05 +0200 Subject: Update `wgpu` for Wasm --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 2fade559..b0f70220 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -25,7 +25,7 @@ rustc-hash = "1.1" log = "0.4" [target.'cfg(target_arch = "wasm32")'.dependencies] -wgpu = { version = "0.16", features = ["webgl"] } +wgpu = { version = "0.17", features = ["webgl"] } [dependencies.twox-hash] version = "1.6" -- cgit From 020fb3c37794fcfa2670c3c0ada949aee95855a0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Sep 2023 01:01:33 +0200 Subject: Fix `iced_wgpu` device selection on Wasm --- core/src/window/redraw_request.rs | 2 +- wgpu/src/window/compositor.rs | 33 +++++++++++++++++++-------------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/core/src/window/redraw_request.rs b/core/src/window/redraw_request.rs index 3b4f0fd3..8a59e83c 100644 --- a/core/src/window/redraw_request.rs +++ b/core/src/window/redraw_request.rs @@ -13,7 +13,7 @@ pub enum RedrawRequest { #[cfg(test)] mod tests { use super::*; - use std::time::{Duration, Instant}; + use crate::time::{Duration, Instant}; #[test] fn ordering() { diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index cd5b20cc..5202c7ef 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -6,8 +6,6 @@ use crate::graphics::compositor; use crate::graphics::{Error, Viewport}; use crate::{Backend, Primitive, Renderer, Settings}; -use futures::stream::{self, StreamExt}; - use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; @@ -95,14 +93,17 @@ impl Compositor { let limits = [wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()]; - let limits = limits.into_iter().map(|limits| wgpu::Limits { - max_bind_groups: 2, - ..limits - }); + let mut limits = limits + .into_iter() + .map(|limits| wgpu::Limits { + max_bind_groups: 2, + ..limits + }) + .into_iter(); - let (device, queue) = stream::iter(limits) - .filter_map(|limits| async { - adapter.request_device( + let (device, queue) = loop { + if let Some(limits) = limits.next() { + let device = adapter.request_device( &wgpu::DeviceDescriptor { label: Some( "iced_wgpu::window::compositor device descriptor", @@ -111,11 +112,15 @@ impl Compositor { limits, }, None, - ).await.ok() - }) - .boxed() - .next() - .await?; + ).await.ok(); + + if let Some(device) = device { + break Some(device); + } + } + + break None; + }?; Some(Compositor { instance, -- cgit From 0b28080d3ebda4dda24c9ac9c38e3195d3499be7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Sep 2023 01:05:17 +0200 Subject: Remove redundant `into_iter` call in `iced_wgpu` --- wgpu/src/window/compositor.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 5202c7ef..cacfeef2 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -93,13 +93,10 @@ impl Compositor { let limits = [wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()]; - let mut limits = limits - .into_iter() - .map(|limits| wgpu::Limits { - max_bind_groups: 2, - ..limits - }) - .into_iter(); + let mut limits = limits.into_iter().map(|limits| wgpu::Limits { + max_bind_groups: 2, + ..limits + }); let (device, queue) = loop { if let Some(limits) = limits.next() { -- cgit From 9b9b37e6f83b5e5a8811feb17b484c6b11fa3b8b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Sep 2023 01:14:22 +0200 Subject: Fix adapter selection loop in `iced_wgpu` --- wgpu/src/window/compositor.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index cacfeef2..9e9c63db 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -98,8 +98,9 @@ impl Compositor { ..limits }); - let (device, queue) = loop { - if let Some(limits) = limits.next() { + let (device, queue) = + loop { + let limits = limits.next()?; let device = adapter.request_device( &wgpu::DeviceDescriptor { label: Some( @@ -114,10 +115,7 @@ impl Compositor { if let Some(device) = device { break Some(device); } - } - - break None; - }?; + }?; Some(Compositor { instance, -- cgit From 832d9f1b01de5a1128f5adcd128f405718c72736 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Sep 2023 01:32:47 +0200 Subject: Introduce `theme::Custom::with_fn` to generate completely custom themes --- style/src/theme.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/style/src/theme.rs b/style/src/theme.rs index 64497181..893d7202 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -1,8 +1,7 @@ //! Use the built-in theme and styles. pub mod palette; -use self::palette::Extended; -pub use self::palette::Palette; +pub use palette::Palette; use crate::application; use crate::button; @@ -40,7 +39,16 @@ pub enum Theme { impl Theme { /// Creates a new custom [`Theme`] from the given [`Palette`]. pub fn custom(palette: Palette) -> Self { - Self::Custom(Box::new(Custom::new(palette))) + Self::custom_with_fn(palette, palette::Extended::generate) + } + + /// Creates a new custom [`Theme`] from the given [`Palette`], with + /// a custom generator of a [`palette::Extended`]. + pub fn custom_with_fn( + palette: Palette, + generate: impl FnOnce(Palette) -> palette::Extended, + ) -> Self { + Self::Custom(Box::new(Custom::with_fn(palette, generate))) } /// Returns the [`Palette`] of the [`Theme`]. @@ -66,15 +74,24 @@ impl Theme { #[derive(Debug, Clone, Copy, PartialEq)] pub struct Custom { palette: Palette, - extended: Extended, + extended: palette::Extended, } impl Custom { /// Creates a [`Custom`] theme from the given [`Palette`]. pub fn new(palette: Palette) -> Self { + Self::with_fn(palette, palette::Extended::generate) + } + + /// Creates a [`Custom`] theme from the given [`Palette`] with + /// a custom generator of a [`palette::Extended`]. + pub fn with_fn( + palette: Palette, + generate: impl FnOnce(Palette) -> palette::Extended, + ) -> Self { Self { palette, - extended: Extended::generate(palette), + extended: generate(palette), } } } -- cgit From 624d5c2c64a174ccdb5236c65ca49556ac3e13ce Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Sep 2023 02:28:02 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d02ef4b..a53c81cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- `Theme::Custom::with_fn` for custom extended palette generation. [#2067](https://github.com/iced-rs/iced/pull/2067) + ### Changed - Updated `wgpu` to `0.17`. [#2065](https://github.com/iced-rs/iced/pull/2065) -- cgit From 8129e2c208d9b13dbd32a309058b0547c723dede Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Sep 2023 08:08:27 +0200 Subject: Implement `draw_paragraph` in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 2 +- tiny_skia/src/text.rs | 158 ++++++++++++++++++++++++++++++----------------- wgpu/src/text.rs | 25 ++++---- 3 files changed, 116 insertions(+), 69 deletions(-) diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index ef587bac..c721d96e 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -405,7 +405,7 @@ impl Backend { let clip_mask = (!physical_bounds.is_within(&clip_bounds)) .then_some(clip_mask as &_); - self.text_pipeline.draw( + self.text_pipeline.draw_cached( content, *bounds + translation, *color, diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 6047a826..3f57f9b1 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -41,16 +41,34 @@ impl Pipeline { pub fn draw_paragraph( &mut self, - _paragraph: ¶graph::Weak, - _position: Point, - _color: Color, - _scale_factor: f32, - _pixels: &mut tiny_skia::PixmapMut<'_>, - _clip_mask: Option<&tiny_skia::Mask>, + paragraph: ¶graph::Weak, + position: Point, + color: Color, + scale_factor: f32, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::Mask>, ) { + use crate::core::text::Paragraph as _; + + let Some(paragraph) = paragraph.upgrade() else { + return; + }; + + draw( + &mut self.font_system.get_mut(), + &mut self.glyph_cache, + paragraph.buffer(), + Rectangle::new(position, paragraph.min_bounds()), + color, + paragraph.horizontal_alignment(), + paragraph.vertical_alignment(), + scale_factor, + pixels, + clip_mask, + ); } - pub fn draw( + pub fn draw_cached( &mut self, content: &str, bounds: Rectangle, @@ -79,54 +97,25 @@ impl Pipeline { let (_, entry) = self.cache.get_mut().allocate(font_system, key); - let max_width = entry.min_bounds.width * scale_factor; - let total_height = entry.min_bounds.height * scale_factor; - - let bounds = bounds * scale_factor; - - let x = match horizontal_alignment { - alignment::Horizontal::Left => bounds.x, - alignment::Horizontal::Center => bounds.x - max_width / 2.0, - alignment::Horizontal::Right => bounds.x - max_width, - }; - - let y = match vertical_alignment { - alignment::Vertical::Top => bounds.y, - alignment::Vertical::Center => bounds.y - total_height / 2.0, - alignment::Vertical::Bottom => bounds.y - total_height, - }; - - let mut swash = cosmic_text::SwashCache::new(); - - for run in entry.buffer.layout_runs() { - for glyph in run.glyphs { - let physical_glyph = glyph.physical((x, y), scale_factor); - - if let Some((buffer, placement)) = self.glyph_cache.allocate( - physical_glyph.cache_key, - color, - font_system, - &mut swash, - ) { - let pixmap = tiny_skia::PixmapRef::from_bytes( - buffer, - placement.width, - placement.height, - ) - .expect("Create glyph pixel map"); - - pixels.draw_pixmap( - physical_glyph.x + placement.left, - physical_glyph.y - placement.top - + (run.line_y * scale_factor).round() as i32, - pixmap, - &tiny_skia::PixmapPaint::default(), - tiny_skia::Transform::identity(), - clip_mask, - ); - } - } - } + let width = entry.min_bounds.width; + let height = entry.min_bounds.height; + + draw( + font_system, + &mut self.glyph_cache, + &entry.buffer, + Rectangle { + width, + height, + ..bounds + }, + color, + horizontal_alignment, + vertical_alignment, + scale_factor, + pixels, + clip_mask, + ); } pub fn trim_cache(&mut self) { @@ -135,6 +124,65 @@ impl Pipeline { } } +fn draw( + font_system: &mut cosmic_text::FontSystem, + glyph_cache: &mut GlyphCache, + buffer: &cosmic_text::Buffer, + bounds: Rectangle, + color: Color, + horizontal_alignment: alignment::Horizontal, + vertical_alignment: alignment::Vertical, + scale_factor: f32, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::Mask>, +) { + let bounds = bounds * scale_factor; + + let x = match horizontal_alignment { + alignment::Horizontal::Left => bounds.x, + alignment::Horizontal::Center => bounds.x - bounds.width / 2.0, + alignment::Horizontal::Right => bounds.x - bounds.width, + }; + + let y = match vertical_alignment { + alignment::Vertical::Top => bounds.y, + alignment::Vertical::Center => bounds.y - bounds.height / 2.0, + alignment::Vertical::Bottom => bounds.y - bounds.height, + }; + + let mut swash = cosmic_text::SwashCache::new(); + + for run in buffer.layout_runs() { + for glyph in run.glyphs { + let physical_glyph = glyph.physical((x, y), scale_factor); + + if let Some((buffer, placement)) = glyph_cache.allocate( + physical_glyph.cache_key, + color, + font_system, + &mut swash, + ) { + let pixmap = tiny_skia::PixmapRef::from_bytes( + buffer, + placement.width, + placement.height, + ) + .expect("Create glyph pixel map"); + + pixels.draw_pixmap( + physical_glyph.x + placement.left, + physical_glyph.y - placement.top + + (run.line_y * scale_factor).round() as i32, + pixmap, + &tiny_skia::PixmapPaint::default(), + tiny_skia::Transform::identity(), + clip_mask, + ); + } + } + } +} + #[derive(Debug, Clone, Default)] struct GlyphCache { entries: FxHashMap< diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index ee352368..a1ec511b 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -159,29 +159,28 @@ impl Pipeline { } }; - let x = bounds.x * scale_factor; - let y = bounds.y * scale_factor; - - let max_width = bounds.width * scale_factor; - let total_height = bounds.height * scale_factor; + let bounds = bounds * scale_factor; let left = match horizontal_alignment { - alignment::Horizontal::Left => x, - alignment::Horizontal::Center => x - max_width / 2.0, - alignment::Horizontal::Right => x - max_width, + alignment::Horizontal::Left => bounds.x, + alignment::Horizontal::Center => { + bounds.x - bounds.width / 2.0 + } + alignment::Horizontal::Right => bounds.x - bounds.width, }; let top = match vertical_alignment { - alignment::Vertical::Top => y, - alignment::Vertical::Center => y - total_height / 2.0, - alignment::Vertical::Bottom => y - total_height, + alignment::Vertical::Top => bounds.y, + alignment::Vertical::Center => { + bounds.y - bounds.height / 2.0 + } + alignment::Vertical::Bottom => bounds.y - bounds.height, }; let section_bounds = Rectangle { x: left, y: top, - width: max_width, - height: total_height, + ..bounds }; let clip_bounds = layer_bounds.intersection(§ion_bounds)?; -- cgit From 1a1da6a1f0ee58d5cdc7365681e0ad5edd0117af Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Sep 2023 08:11:36 +0200 Subject: Remove unnecessary mutable reference in `iced_tiny_skia` --- tiny_skia/src/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 3f57f9b1..2bf35e76 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -55,7 +55,7 @@ impl Pipeline { }; draw( - &mut self.font_system.get_mut(), + self.font_system.get_mut(), &mut self.glyph_cache, paragraph.buffer(), Rectangle::new(position, paragraph.min_bounds()), -- cgit From 3a44ad3c737cf6cff6f04e03a7778d75fcc0c719 Mon Sep 17 00:00:00 2001 From: Akshay Raina <104209297+akshayr-mecha@users.noreply.github.com> Date: Sat, 2 Sep 2023 02:03:39 +0530 Subject: fix(examples-styling): fixed checkbox and toggler getting hidden behind scrollbar Column inside scrollable is having Length::Fill so it is taking entire width thus hiding check box and toggler. Added fixed width to scrollable so Lenth::Fill will be relative to fixed width --- examples/styling/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index f8a4c80a..b457df25 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -108,6 +108,7 @@ impl Sandbox for Styling { column!["Scroll me!", vertical_space(800), "You did it!"] .width(Length::Fill), ) + .width(300) .height(100); let checkbox = checkbox( -- cgit From 20bf01a551b967f6f8be4c38ef01d14a690d8835 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Date: Sun, 3 Sep 2023 08:28:03 +0200 Subject: Use `Length::Fill` instead of fixed length in `styling` example --- examples/styling/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index b457df25..51538ec2 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -108,7 +108,7 @@ impl Sandbox for Styling { column!["Scroll me!", vertical_space(800), "You did it!"] .width(Length::Fill), ) - .width(300) + .width(Length::Fill) .height(100); let checkbox = checkbox( -- cgit From aed06ac208d1e80b13c5952adbff78dd7d02c025 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 21 Aug 2023 13:32:44 -0400 Subject: Support automatic style type casting for Buttons. Changes the signature of Button::style to use `impl Into<...>` instead of taking the style sheet itself. (Matches other widgets). --- widget/src/button.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/widget/src/button.rs b/widget/src/button.rs index 5727c631..18a95c9e 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -119,9 +119,9 @@ where /// Sets the style variant of this [`Button`]. pub fn style( mut self, - style: ::Style, + style: impl Into<::Style>, ) -> Self { - self.style = style; + self.style = style.into(); self } } -- cgit From 28252e64086ad99897e881aa5e27e64654e9bafb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Sep 2023 08:41:08 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a53c81cb..8be92a28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated `wgpu` to `0.17`. [#2065](https://github.com/iced-rs/iced/pull/2065) +- Changed `Button::style` to take an `impl Into<...>` for consistency. [#2046](https://github.com/iced-rs/iced/pull/2046) Many thanks to... -- Add your here +- @dtzxporter ## [0.10.0] - 2023-07-28 ### Added -- cgit From 89d31913db5d1e46b19b265bf48faccfc9869066 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Sep 2023 08:33:46 +0200 Subject: Update `CHANGELOG` --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a53c81cb..68713158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated `wgpu` to `0.17`. [#2065](https://github.com/iced-rs/iced/pull/2065) +### Fixed +- Missing `width` attribute in `styling` example. [#2062](https://github.com/iced-rs/iced/pull/2062) + Many thanks to... -- Add your here +- @akshayr-mecha ## [0.10.0] - 2023-07-28 ### Added -- cgit From 982ea578be1e0b934e75b41cb8d65d2ffe05558a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Sep 2023 08:43:58 +0200 Subject: Stop verifying the `CHANGELOG` on every PR It's a real pain to keep it up-to-date. Way easier to generate it with a script before release. --- .github/workflows/verify.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .github/workflows/verify.yml diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml deleted file mode 100644 index 089dd5c9..00000000 --- a/.github/workflows/verify.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Verify -on: - pull_request: - branches: - - master -jobs: - changelog: - 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 origin/master HEAD -- CHANGELOG.md - - name: Check `CHANGELOG.md` thanks the PR author - 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.login }}' -- cgit From 76cec1b1fd533dda37aa53c40ef7665ed3b406b6 Mon Sep 17 00:00:00 2001 From: David Huculak Date: Sun, 3 Sep 2023 19:32:38 -0400 Subject: use @interpolate(flat) attribute as per the WebGPU spec: User-defined vertex outputs and fragment inputs of scalar or vector integer type must always be specified as @interpolate(flat) https://www.w3.org/TR/WGSL/#interpolation --- wgpu/src/shader/quad.wgsl | 22 +++++++++++----------- wgpu/src/shader/triangle.wgsl | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index fb402158..87055339 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -147,12 +147,12 @@ fn solid_fs_main( struct GradientVertexInput { @location(0) v_pos: vec2, - @location(1) colors_1: vec4, - @location(2) colors_2: vec4, - @location(3) colors_3: vec4, - @location(4) colors_4: vec4, - @location(5) offsets: vec4, - @location(6) direction: vec4, + @location(1) @interpolate(flat) colors_1: vec4, + @location(2) @interpolate(flat) colors_2: vec4, + @location(3) @interpolate(flat) colors_3: vec4, + @location(4) @interpolate(flat) colors_4: vec4, + @location(5) @interpolate(flat) offsets: vec4, + @location(6) @interpolate(flat) direction: vec4, @location(7) position_and_scale: vec4, @location(8) border_color: vec4, @location(9) border_radius: vec4, @@ -161,11 +161,11 @@ struct GradientVertexInput { struct GradientVertexOutput { @builtin(position) position: vec4, - @location(1) colors_1: vec4, - @location(2) colors_2: vec4, - @location(3) colors_3: vec4, - @location(4) colors_4: vec4, - @location(5) offsets: vec4, + @location(1) @interpolate(flat) colors_1: vec4, + @location(2) @interpolate(flat) colors_2: vec4, + @location(3) @interpolate(flat) colors_3: vec4, + @location(4) @interpolate(flat) colors_4: vec4, + @location(5) @interpolate(flat) offsets: vec4, @location(6) direction: vec4, @location(7) position_and_scale: vec4, @location(8) border_color: vec4, diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl index 9f512d14..3a2b9845 100644 --- a/wgpu/src/shader/triangle.wgsl +++ b/wgpu/src/shader/triangle.wgsl @@ -38,22 +38,22 @@ fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4 { struct GradientVertexInput { @location(0) v_pos: vec2, - @location(1) colors_1: vec4, - @location(2) colors_2: vec4, - @location(3) colors_3: vec4, - @location(4) colors_4: vec4, - @location(5) offsets: vec4, + @location(1) @interpolate(flat) colors_1: vec4, + @location(2) @interpolate(flat) colors_2: vec4, + @location(3) @interpolate(flat) colors_3: vec4, + @location(4) @interpolate(flat) colors_4: vec4, + @location(5) @interpolate(flat) offsets: vec4, @location(6) direction: vec4, } struct GradientVertexOutput { @builtin(position) position: vec4, @location(0) raw_position: vec2, - @location(1) colors_1: vec4, - @location(2) colors_2: vec4, - @location(3) colors_3: vec4, - @location(4) colors_4: vec4, - @location(5) offsets: vec4, + @location(1) @interpolate(flat) colors_1: vec4, + @location(2) @interpolate(flat) colors_2: vec4, + @location(3) @interpolate(flat) colors_3: vec4, + @location(4) @interpolate(flat) colors_4: vec4, + @location(5) @interpolate(flat) offsets: vec4, @location(6) direction: vec4, } -- cgit From 34495bba1c1ffaa4ea2bab46103b5d66e333c51e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 4 Sep 2023 02:55:09 +0200 Subject: Introduce `keyed::Column` widget --- core/src/widget/tree.rs | 82 +++++++++++ examples/todos/Cargo.toml | 4 +- examples/todos/src/main.rs | 24 ++-- graphics/src/text/paragraph.rs | 2 + widget/src/helpers.rs | 13 +- widget/src/keyed.rs | 55 +++++++ widget/src/keyed/column.rs | 320 +++++++++++++++++++++++++++++++++++++++++ widget/src/lib.rs | 1 + 8 files changed, 490 insertions(+), 11 deletions(-) create mode 100644 widget/src/keyed.rs create mode 100644 widget/src/keyed/column.rs diff --git a/core/src/widget/tree.rs b/core/src/widget/tree.rs index 0af40c33..202cca9a 100644 --- a/core/src/widget/tree.rs +++ b/core/src/widget/tree.rs @@ -107,6 +107,88 @@ impl Tree { } } +/// Reconciliates the `current_children` with the provided list of widgets using +/// custom logic both for diffing and creating new widget state. +/// +/// The algorithm will try to minimize the impact of diffing by querying the +/// `maybe_changed` closure. +pub fn diff_children_custom_with_search( + current_children: &mut Vec, + new_children: &[T], + diff: impl Fn(&mut Tree, &T), + maybe_changed: impl Fn(usize) -> bool, + new_state: impl Fn(&T) -> Tree, +) { + if new_children.is_empty() { + current_children.clear(); + return; + } + + if current_children.is_empty() { + current_children.extend(new_children.iter().map(new_state)); + return; + } + + let first_maybe_changed = maybe_changed(0); + let last_maybe_changed = maybe_changed(current_children.len() - 1); + + if current_children.len() > new_children.len() { + if !first_maybe_changed && last_maybe_changed { + current_children.truncate(new_children.len()); + } else { + let difference_index = if first_maybe_changed { + 0 + } else { + (1..current_children.len()) + .find(|&i| maybe_changed(i)) + .unwrap_or(0) + }; + + let _ = current_children.splice( + difference_index + ..difference_index + + (current_children.len() - new_children.len()), + std::iter::empty(), + ); + } + } + + if current_children.len() < new_children.len() { + let first_maybe_changed = maybe_changed(0); + let last_maybe_changed = maybe_changed(current_children.len() - 1); + + if !first_maybe_changed && last_maybe_changed { + current_children.extend( + new_children[current_children.len()..].iter().map(new_state), + ); + } else { + let difference_index = if first_maybe_changed { + 0 + } else { + (1..current_children.len()) + .find(|&i| maybe_changed(i)) + .unwrap_or(0) + }; + + let _ = current_children.splice( + difference_index..difference_index, + new_children[difference_index + ..difference_index + + (new_children.len() - current_children.len())] + .iter() + .map(new_state), + ); + } + } + + // TODO: Merge loop with extend logic (?) + for (child_state, new) in + current_children.iter_mut().zip(new_children.iter()) + { + diff(child_state, new); + } +} + /// The identifier of some widget state. #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] pub struct Tag(any::TypeId); diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index 7ad4d558..4a35cfe6 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -7,9 +7,11 @@ publish = false [dependencies] iced = { path = "../..", features = ["async-std", "debug"] } +uuid = { version = "1.0", features = ["v4", "fast-rng", "serde"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -once_cell = "1.15" +once_cell = "1.0" +tracing-subscriber = "0.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] async-std = "1.0" diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 6ad7b4fb..1dd8a307 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -5,8 +5,8 @@ use iced::keyboard::{self, KeyCode, Modifiers}; use iced::subscription; use iced::theme::{self, Theme}; use iced::widget::{ - self, button, checkbox, column, container, row, scrollable, text, - text_input, Text, + self, button, checkbox, column, container, keyed_column, row, scrollable, + text, text_input, Text, }; use iced::window; use iced::{Application, Element}; @@ -14,10 +14,13 @@ use iced::{Color, Command, Length, Settings, Subscription}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; +use uuid::Uuid; static INPUT_ID: Lazy = Lazy::new(text_input::Id::unique); pub fn main() -> iced::Result { + tracing_subscriber::fmt::init(); + Todos::run(Settings { window: window::Settings { size: (500, 800), @@ -222,17 +225,19 @@ impl Application for Todos { tasks.iter().filter(|task| filter.matches(task)); let tasks: Element<_> = if filtered_tasks.count() > 0 { - column( + keyed_column( tasks .iter() .enumerate() .filter(|(_, task)| filter.matches(task)) .map(|(i, task)| { - task.view(i).map(move |message| { - Message::TaskMessage(i, message) - }) - }) - .collect(), + ( + task.id, + task.view(i).map(move |message| { + Message::TaskMessage(i, message) + }), + ) + }), ) .spacing(10) .into() @@ -295,6 +300,8 @@ impl Application for Todos { #[derive(Debug, Clone, Serialize, Deserialize)] struct Task { + #[serde(default = "Uuid::new_v4")] + id: Uuid, description: String, completed: bool, @@ -330,6 +337,7 @@ impl Task { fn new(description: String) -> Self { Task { + id: Uuid::new_v4(), description, completed: false, state: TaskState::Idle, diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index d99b8412..ee7c04c8 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -27,6 +27,8 @@ impl Paragraph { } pub fn with_text(text: Text<'_, Font>, font_system: &FontSystem) -> Self { + log::trace!("\nAllocating paragraph: {}", text.content); + let mut font_system = font_system.write(); let mut buffer = cosmic_text::Buffer::new( diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 9c3c83a9..c885d724 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -6,6 +6,7 @@ use crate::container::{self, Container}; use crate::core; use crate::core::widget::operation; use crate::core::{Element, Length, Pixels}; +use crate::keyed; use crate::overlay; use crate::pick_list::{self, PickList}; use crate::progress_bar::{self, ProgressBar}; @@ -63,14 +64,22 @@ where } /// Creates a new [`Column`] with the given children. -/// -/// [`Column`]: widget::Column pub fn column( children: Vec>, ) -> Column<'_, Message, Renderer> { Column::with_children(children) } +/// Creates a new [`keyed::Column`] with the given children. +pub fn keyed_column<'a, Key, Message, Renderer>( + children: impl IntoIterator)>, +) -> keyed::Column<'a, Key, Message, Renderer> +where + Key: Copy + PartialEq, +{ + keyed::Column::with_children(children) +} + /// Creates a new [`Row`] with the given children. /// /// [`Row`]: widget::Row diff --git a/widget/src/keyed.rs b/widget/src/keyed.rs new file mode 100644 index 00000000..55019535 --- /dev/null +++ b/widget/src/keyed.rs @@ -0,0 +1,55 @@ +//! Use widgets that can provide hints to ensure continuity. +//! +//! # What is continuity? +//! Continuity is the feeling of persistence of state. +//! +//! In a graphical user interface, users expect widgets to have a +//! certain degree of continuous state. For instance, a text input +//! that is focused should stay focused even if the widget tree +//! changes slightly. +//! +//! Continuity is tricky in `iced` and the Elm Architecture because +//! the whole widget tree is rebuilt during every `view` call. This is +//! very convenient from a developer perspective because you can build +//! extremely dynamic interfaces without worrying about changing state. +//! +//! However, the tradeoff is that determining what changed becomes hard +//! for `iced`. If you have a list of things, adding an element at the +//! top may cause a loss of continuity on every element on the list! +//! +//! # How can we keep continuity? +//! The good news is that user interfaces generally have a static widget +//! structure. This structure can be relied on to ensure some degree of +//! continuity. `iced` already does this. +//! +//! However, sometimes you have a certain part of your interface that is +//! quite dynamic. For instance, a list of things where items may be added +//! or removed at any place. +//! +//! There are different ways to mitigate this during the reconciliation +//! stage, but they involve comparing trees at certain depths and +//! backtracking... Quite computationally expensive. +//! +//! One approach that is cheaper consists in letting the user provide some hints +//! about the identities of the different widgets so that they can be compared +//! directly without going deeper. +//! +//! The widgets in this module will all ask for a "hint" of some sort. In order +//! to help them keep continuity, you need to make sure the hint stays the same +//! for the same items in your user interface between `view` calls. +pub mod column; + +pub use column::Column; + +/// Creates a [`Column`] with the given children. +/// +/// [`Column`]: widget::Column +#[macro_export] +macro_rules! keyed_column { + () => ( + $crate::Column::new() + ); + ($($x:expr),+ $(,)?) => ( + $crate::keyed::Column::with_children(vec![$($crate::core::Element::from($x)),+]) + ); +} diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs new file mode 100644 index 00000000..19016679 --- /dev/null +++ b/widget/src/keyed/column.rs @@ -0,0 +1,320 @@ +//! Distribute content vertically. +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget::tree::{self, Tree}; +use crate::core::widget::Operation; +use crate::core::{ + Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, + Shell, Widget, +}; + +/// A container that distributes its contents vertically. +#[allow(missing_debug_implementations)] +pub struct Column<'a, Key, Message, Renderer = crate::Renderer> +where + Key: Copy + PartialEq, +{ + spacing: f32, + padding: Padding, + width: Length, + height: Length, + max_width: f32, + align_items: Alignment, + keys: Vec, + children: Vec>, +} + +impl<'a, Key, Message, Renderer> Column<'a, Key, Message, Renderer> +where + Key: Copy + PartialEq, +{ + /// Creates an empty [`Column`]. + pub fn new() -> Self { + Self::with_children(Vec::new()) + } + + /// Creates a [`Column`] with the given elements. + pub fn with_children( + children: impl IntoIterator)>, + ) -> Self { + let (keys, children) = children.into_iter().fold( + (Vec::new(), Vec::new()), + |(mut keys, mut children), (key, child)| { + keys.push(key); + children.push(child); + + (keys, children) + }, + ); + + Column { + spacing: 0.0, + padding: Padding::ZERO, + width: Length::Shrink, + height: Length::Shrink, + max_width: f32::INFINITY, + align_items: Alignment::Start, + keys, + children, + } + } + + /// Sets the vertical spacing _between_ elements. + /// + /// Custom margins per element do not exist in iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, amount: impl Into) -> Self { + self.spacing = amount.into().0; + self + } + + /// Sets the [`Padding`] of the [`Column`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the width of the [`Column`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Sets the height of the [`Column`]. + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + + /// Sets the maximum width of the [`Column`]. + pub fn max_width(mut self, max_width: impl Into) -> Self { + self.max_width = max_width.into().0; + self + } + + /// Sets the horizontal alignment of the contents of the [`Column`] . + pub fn align_items(mut self, align: Alignment) -> Self { + self.align_items = align; + self + } + + /// Adds an element to the [`Column`]. + pub fn push( + mut self, + key: Key, + child: impl Into>, + ) -> Self { + self.keys.push(key); + self.children.push(child.into()); + self + } +} + +impl<'a, Key, Message, Renderer> Default for Column<'a, Key, Message, Renderer> +where + Key: Copy + PartialEq, +{ + fn default() -> Self { + Self::new() + } +} + +struct State +where + Key: Copy + PartialEq, +{ + keys: Vec, +} + +impl<'a, Key, Message, Renderer> Widget + for Column<'a, Key, Message, Renderer> +where + Renderer: crate::core::Renderer, + Key: Copy + PartialEq + 'static, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(State { + keys: self.keys.clone(), + }) + } + + fn children(&self) -> Vec { + self.children.iter().map(Tree::new).collect() + } + + fn diff(&self, tree: &mut Tree) { + let Tree { + state, children, .. + } = tree; + + let state = state.downcast_mut::>(); + + tree::diff_children_custom_with_search( + children, + &self.children, + |tree, child| child.as_widget().diff(tree), + |index| { + self.keys.get(index).or_else(|| self.keys.last()).copied() + != Some(state.keys[index]) + }, + |child| Tree::new(child.as_widget()), + ); + + if state.keys != self.keys { + state.keys = self.keys.clone(); + } + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits + .max_width(self.max_width) + .width(self.width) + .height(self.height); + + layout::flex::resolve( + layout::flex::Axis::Vertical, + renderer, + &limits, + self.padding, + self.spacing, + self.align_items, + &self.children, + &mut tree.children, + ) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation, + ) { + operation.container(None, layout.bounds(), &mut |operation| { + self.children + .iter() + .zip(&mut tree.children) + .zip(layout.children()) + .for_each(|((child, state), layout)| { + child + .as_widget() + .operate(state, layout, renderer, operation); + }) + }); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + self.children + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget_mut().on_event( + state, + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ) + }) + .fold(event::Status::Ignored, event::Status::merge) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget().mouse_interaction( + state, layout, cursor, viewport, renderer, + ) + }) + .max() + .unwrap_or_default() + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + for ((child, state), layout) in self + .children + .iter() + .zip(&tree.children) + .zip(layout.children()) + { + child + .as_widget() + .draw(state, renderer, theme, style, layout, cursor, viewport); + } + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + overlay::from_children(&mut self.children, tree, layout, renderer) + } +} + +impl<'a, Key, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Key: Copy + PartialEq + 'static, + Message: 'a, + Renderer: crate::core::Renderer + 'a, +{ + fn from(column: Column<'a, Key, Message, Renderer>) -> Self { + Self::new(column) + } +} diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 316e8829..707fec04 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -29,6 +29,7 @@ pub mod button; pub mod checkbox; pub mod combo_box; pub mod container; +pub mod keyed; pub mod overlay; pub mod pane_grid; pub mod pick_list; -- cgit From 837529bc995a728300c61fc102474cc31f7a6500 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 4 Sep 2023 03:02:46 +0200 Subject: Fix Wasm build of `todos` example --- examples/todos/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index 4a35cfe6..7292f665 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -18,6 +18,7 @@ async-std = "1.0" directories-next = "2.0" [target.'cfg(target_arch = "wasm32")'.dependencies] +uuid = { version = "1.0", features = ["js"] } web-sys = { version = "0.3", features = ["Window", "Storage"] } wasm-timer = "0.2" -- cgit From bb49a22996f39e32c3ced0c2d80c2137aed7a0ea Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 4 Sep 2023 03:28:45 +0200 Subject: Enable WebGPU backend in `wgpu` by default instead of WebGL Instead, we expose a new `webgl` feature. --- Cargo.toml | 2 ++ renderer/Cargo.toml | 1 + wgpu/Cargo.toml | 4 +--- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 71484b7a..3b2ef547 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,8 @@ palette = ["iced_core/palette"] system = ["iced_winit/system"] # Enables broken "sRGB linear" blending to reproduce color management of the Web web-colors = ["iced_renderer/web-colors"] +# Enables the WebGL backend, replacing WebGPU +webgl = ["iced_renderer/webgl"] # Enables the advanced module advanced = [] diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 89326d73..2c88cf31 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -17,6 +17,7 @@ svg = ["iced_tiny_skia/svg", "iced_wgpu?/svg"] geometry = ["iced_graphics/geometry", "iced_tiny_skia/geometry", "iced_wgpu?/geometry"] tracing = ["iced_wgpu?/tracing"] web-colors = ["iced_wgpu?/web-colors"] +webgl = ["iced_wgpu?/webgl"] [dependencies] raw-window-handle = "0.5" diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index b0f70220..49c62673 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -12,6 +12,7 @@ geometry = ["iced_graphics/geometry", "lyon"] image = ["iced_graphics/image"] svg = ["resvg"] web-colors = ["iced_graphics/web-colors"] +webgl = ["wgpu/webgl"] [dependencies] wgpu = "0.17" @@ -24,9 +25,6 @@ once_cell = "1.0" rustc-hash = "1.1" log = "0.4" -[target.'cfg(target_arch = "wasm32")'.dependencies] -wgpu = { version = "0.17", features = ["webgl"] } - [dependencies.twox-hash] version = "1.6" default-features = false -- cgit From 685e44587c8c89be455838d6b96f3b762da836f9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 4 Sep 2023 03:46:41 +0200 Subject: Enable unstable `web-sys` APIs on CI --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a9a9b3f9..ac8d27f9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,6 +25,8 @@ jobs: web: runs-on: ubuntu-latest + env: + RUSTFLAGS: --cfg=web_sys_unstable_apis steps: - uses: hecrj/setup-rust-action@v1 with: -- cgit From ef429fbea6e39efcfdc58fdd8a2fe365fd5314d9 Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Mon, 4 Sep 2023 02:58:54 -0400 Subject: Ensure LineHeight > 0.0 for the WGPU renderer --- wgpu/src/text.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index fb13545d..08a32b5e 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -96,7 +96,8 @@ impl Pipeline { section .line_height .to_absolute(Pixels(section.size)), - ), + ) + .max(f32::MIN_POSITIVE), font: section.font, bounds: Size { width: section.bounds.width, @@ -238,7 +239,8 @@ impl Pipeline { ) -> Size { let mut cache = self.cache.borrow_mut(); - let line_height = f32::from(line_height.to_absolute(Pixels(size))); + let line_height = f32::from(line_height.to_absolute(Pixels(size))) + .max(f32::MIN_POSITIVE); let (_, entry) = cache.allocate( &mut self.font_system.borrow_mut(), @@ -269,7 +271,8 @@ impl Pipeline { ) -> Option { let mut cache = self.cache.borrow_mut(); - let line_height = f32::from(line_height.to_absolute(Pixels(size))); + let line_height = f32::from(line_height.to_absolute(Pixels(size))) + .max(f32::MIN_POSITIVE); let (_, entry) = cache.allocate( &mut self.font_system.borrow_mut(), -- cgit From f468e25d0c67a01ee79d892f6e8ba9be019f06c7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 4 Sep 2023 12:58:41 +0200 Subject: Use workspace dependencies and package inheritance We are also taking this as a chance to synchronize the versions of all the crates! Because of this, we will skip the `0.11` version. --- Cargo.toml | 135 +++++++++++++++++++++++--------- core/Cargo.toml | 30 +++---- examples/arc/Cargo.toml | 3 +- examples/bezier_tool/Cargo.toml | 3 +- examples/checkbox/Cargo.toml | 2 +- examples/clock/Cargo.toml | 6 +- examples/color_palette/Cargo.toml | 6 +- examples/combo_box/Cargo.toml | 3 +- examples/component/Cargo.toml | 3 +- examples/counter/Cargo.toml | 2 +- examples/custom_quad/Cargo.toml | 3 +- examples/custom_widget/Cargo.toml | 3 +- examples/download_progress/Cargo.toml | 3 +- examples/events/Cargo.toml | 3 +- examples/exit/Cargo.toml | 2 +- examples/game_of_life/Cargo.toml | 10 ++- examples/game_of_life/src/main.rs | 2 +- examples/geometry/Cargo.toml | 3 +- examples/integration/Cargo.toml | 13 +-- examples/integration/src/main.rs | 2 +- examples/lazy/Cargo.toml | 3 +- examples/loading_spinners/Cargo.toml | 8 +- examples/modal/Cargo.toml | 3 +- examples/multitouch/Cargo.toml | 7 +- examples/multitouch/src/main.rs | 2 +- examples/pane_grid/Cargo.toml | 3 +- examples/pick_list/Cargo.toml | 3 +- examples/pokedex/Cargo.toml | 4 +- examples/progress_bar/Cargo.toml | 2 +- examples/qr_code/Cargo.toml | 3 +- examples/screenshot/Cargo.toml | 8 +- examples/screenshot/src/main.rs | 2 +- examples/scrollable/Cargo.toml | 6 +- examples/sierpinski_triangle/Cargo.toml | 6 +- examples/slider/Cargo.toml | 2 +- examples/solar_system/Cargo.toml | 6 +- examples/solar_system/src/main.rs | 2 +- examples/stopwatch/Cargo.toml | 3 +- examples/styling/Cargo.toml | 2 +- examples/svg/Cargo.toml | 3 +- examples/system_information/Cargo.toml | 6 +- examples/toast/Cargo.toml | 3 +- examples/todos/Cargo.toml | 12 +-- examples/tooltip/Cargo.toml | 3 +- examples/tour/Cargo.toml | 6 +- examples/tour/src/main.rs | 2 +- examples/url_handler/Cargo.toml | 2 +- examples/visible_bounds/Cargo.toml | 6 +- examples/websocket/Cargo.toml | 12 +-- futures/Cargo.toml | 61 +++++++-------- graphics/Cargo.toml | 62 +++++++-------- renderer/Cargo.toml | 39 ++++----- runtime/Cargo.toml | 28 +++---- src/window/icon.rs | 9 ++- style/Cargo.toml | 30 +++---- tiny_skia/Cargo.toml | 54 ++++++------- wgpu/Cargo.toml | 90 ++++++++++----------- widget/Cargo.toml | 57 ++++++-------- winit/Cargo.toml | 91 +++++++-------------- 59 files changed, 456 insertions(+), 432 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3b2ef547..af74a3cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,22 +1,28 @@ [package] name = "iced" -version = "0.10.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" description = "A cross-platform GUI library inspired by Elm" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced" -readme = "README.md" -keywords = ["gui", "ui", "graphics", "interface", "widgets"] -categories = ["gui"] +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +all-features = true + +[badges] +maintenance = { status = "actively-developed" } [features] default = ["wgpu"] # Enable the `wgpu` GPU-accelerated renderer backend wgpu = ["iced_renderer/wgpu"] # Enables the `Image` widget -image = ["iced_widget/image", "image_rs"] +image = ["iced_widget/image", "dep:image"] # Enables the `Svg` widget svg = ["iced_widget/svg"] # Enables the `Canvas` widget @@ -44,8 +50,28 @@ webgl = ["iced_renderer/webgl"] # Enables the advanced module advanced = [] -[badges] -maintenance = { status = "actively-developed" } +[dependencies] +iced_core.workspace = true +iced_futures.workspace = true +iced_renderer.workspace = true +iced_widget.workspace = true +iced_winit.features = ["application"] +iced_winit.workspace = true + +thiserror.workspace = true + +image.workspace = true +image.optional = true + +[profile.release-opt] +inherits = "release" +codegen-units = 1 +debug = false +lto = true +incremental = false +opt-level = 3 +overflow-checks = false +strip = "debuginfo" [workspace] members = [ @@ -62,29 +88,66 @@ members = [ "examples/*", ] -[dependencies] -iced_core = { version = "0.10", path = "core" } -iced_futures = { version = "0.7", path = "futures" } -iced_renderer = { version = "0.1", path = "renderer" } -iced_widget = { version = "0.1", path = "widget" } -iced_winit = { version = "0.10", path = "winit", features = ["application"] } -thiserror = "1" +[workspace.package] +version = "0.12.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2021" +license = "MIT" +repository = "https://github.com/iced-rs/iced" +homepage = "https://iced.rs" +categories = ["gui"] +keywords = ["gui", "ui", "graphics", "interface", "widgets"] -[dependencies.image_rs] -version = "0.24" -package = "image" -optional = true +[workspace.dependencies] +iced = { version = "0.12", path = "." } +iced_core = { version = "0.12", path = "core" } +iced_futures = { version = "0.12", path = "futures" } +iced_graphics = { version = "0.12", path = "graphics" } +iced_renderer = { version = "0.12", path = "renderer" } +iced_runtime = { version = "0.12", path = "runtime" } +iced_style = { version = "0.12", path = "style" } +iced_tiny_skia = { version = "0.12", path = "tiny_skia" } +iced_wgpu = { version = "0.12", path = "wgpu" } +iced_widget = { version = "0.12", path = "widget" } +iced_winit = { version = "0.12", path = "winit" } -[package.metadata.docs.rs] -rustdoc-args = ["--cfg", "docsrs"] -all-features = true - -[profile.release-opt] -inherits = "release" -codegen-units = 1 -debug = false -lto = true -incremental = false -opt-level = 3 -overflow-checks = false -strip = "debuginfo" +async-std = "1.0" +bitflags = "1.0" +bytemuck = { version = "1.0", features = ["derive"] } +cosmic-text = "0.9" +futures = "0.3" +glam = "0.24" +glyphon = { git = "https://github.com/grovesNL/glyphon.git", rev = "20f0f8fa80e0d0df4c63634ce9176fa489546ca9" } +guillotiere = "0.6" +half = "2.2" +image = "0.24" +instant = "0.1" +kamadak-exif = "0.5" +kurbo = "0.9" +log = "0.4" +lyon = "1.0" +lyon_path = "1.0" +num-traits = "0.2" +once_cell = "1.0" +ouroboros = "0.17" +palette = "0.7" +qrcode = { version = "0.12", default-features = false } +raw-window-handle = "0.5" +resvg = "0.35" +rustc-hash = "1.0" +smol = "1.0" +softbuffer = "0.2" +sysinfo = "0.28" +thiserror = "1.0" +tiny-skia = "0.10" +tokio = "1.0" +tracing = "0.1" +twox-hash = { version = "1.0", default-features = false } +unicode-segmentation = "1.0" +wasm-bindgen-futures = "0.4" +wasm-timer = "0.2" +web-sys = "0.3" +wgpu = "0.17" +winapi = "0.3" +window_clipboard = "0.3" +winit = { git = "https://github.com/iced-rs/winit.git", rev = "c52db2045d0a2f1b8d9923870de1d4ab1994146e", default-features = false } diff --git a/core/Cargo.toml b/core/Cargo.toml index 8bb37309..8859e91e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,24 +1,26 @@ [package] name = "iced_core" -version = "0.10.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -description = "The essential concepts of Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" +description = "The essential ideas of iced" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true [dependencies] -bitflags = "1.2" -thiserror = "1" -log = "0.4.17" -twox-hash = { version = "1.5", default-features = false } +bitflags.workspace = true +log.workspace = true +thiserror.workspace = true +twox-hash.workspace = true -[dependencies.palette] -version = "0.7" -optional = true +palette.workspace = true +palette.optional = true [target.'cfg(target_arch = "wasm32")'.dependencies] -instant = "0.1" +instant.workspace = true [dev-dependencies] approx = "0.5" diff --git a/examples/arc/Cargo.toml b/examples/arc/Cargo.toml index e6e74363..5012ff82 100644 --- a/examples/arc/Cargo.toml +++ b/examples/arc/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["canvas", "tokio", "debug"] } +iced.workspace = true +iced.features = ["canvas", "tokio", "debug"] diff --git a/examples/bezier_tool/Cargo.toml b/examples/bezier_tool/Cargo.toml index 890e3027..b2547ff1 100644 --- a/examples/bezier_tool/Cargo.toml +++ b/examples/bezier_tool/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["canvas"] } +iced.workspace = true +iced.features = ["canvas"] diff --git a/examples/checkbox/Cargo.toml b/examples/checkbox/Cargo.toml index dde8f910..1e027c4c 100644 --- a/examples/checkbox/Cargo.toml +++ b/examples/checkbox/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } +iced.workspace = true diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index 5e869eb5..2d3d5908 100644 --- a/examples/clock/Cargo.toml +++ b/examples/clock/Cargo.toml @@ -6,5 +6,7 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["canvas", "tokio", "debug"] } -time = { version = "0.3.5", features = ["local-offset"] } +iced.workspace = true +iced.features = ["canvas", "tokio", "debug"] + +time = { version = "0.3", features = ["local-offset"] } diff --git a/examples/color_palette/Cargo.toml b/examples/color_palette/Cargo.toml index 3be732bb..2da6c6ed 100644 --- a/examples/color_palette/Cargo.toml +++ b/examples/color_palette/Cargo.toml @@ -6,5 +6,7 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["canvas", "palette"] } -palette = "0.7.0" +iced.workspace = true +iced.features = ["canvas", "palette"] + +palette.workspace = true diff --git a/examples/combo_box/Cargo.toml b/examples/combo_box/Cargo.toml index be1b5e32..0f5ecf2a 100644 --- a/examples/combo_box/Cargo.toml +++ b/examples/combo_box/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug"] } +iced.workspace = true +iced.features = ["debug"] diff --git a/examples/component/Cargo.toml b/examples/component/Cargo.toml index 9db1e6b4..83b7b8a4 100644 --- a/examples/component/Cargo.toml +++ b/examples/component/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug", "lazy"] } +iced.workspace = true +iced.features = ["debug", "lazy"] diff --git a/examples/counter/Cargo.toml b/examples/counter/Cargo.toml index e31f440f..1e9bba6b 100644 --- a/examples/counter/Cargo.toml +++ b/examples/counter/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } +iced.workspace = true diff --git a/examples/custom_quad/Cargo.toml b/examples/custom_quad/Cargo.toml index f097c2dd..31b5055d 100644 --- a/examples/custom_quad/Cargo.toml +++ b/examples/custom_quad/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["advanced"] } +iced.workspace = true +iced.features = ["advanced"] diff --git a/examples/custom_widget/Cargo.toml b/examples/custom_widget/Cargo.toml index dda0efe8..1e94bc52 100644 --- a/examples/custom_widget/Cargo.toml +++ b/examples/custom_widget/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["advanced"] } +iced.workspace = true +iced.features = ["advanced"] diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml index 212832f4..18a49f66 100644 --- a/examples/download_progress/Cargo.toml +++ b/examples/download_progress/Cargo.toml @@ -6,7 +6,8 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["tokio"] } +iced.workspace = true +iced.features = ["tokio"] [dependencies.reqwest] version = "0.11" diff --git a/examples/events/Cargo.toml b/examples/events/Cargo.toml index 15ffc0af..87315a10 100644 --- a/examples/events/Cargo.toml +++ b/examples/events/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug"] } +iced.workspace = true +iced.features = ["debug"] diff --git a/examples/exit/Cargo.toml b/examples/exit/Cargo.toml index 34d0789a..b06fbadc 100644 --- a/examples/exit/Cargo.toml +++ b/examples/exit/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } +iced.workspace = true diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml index 6de45db6..f8d21c65 100644 --- a/examples/game_of_life/Cargo.toml +++ b/examples/game_of_life/Cargo.toml @@ -6,8 +6,10 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["canvas", "tokio", "debug"] } -tokio = { version = "1.0", features = ["sync"] } +iced.workspace = true +iced.features = ["debug", "canvas", "tokio"] + itertools = "0.9" -rustc-hash = "1.1" -env_logger = "0.10" +rustc-hash.workspace = true +tokio = { workspace = true, features = ["sync"] } +tracing-subscriber = "0.3" diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index e951d734..a2038f12 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -18,7 +18,7 @@ use iced::{ use std::time::{Duration, Instant}; pub fn main() -> iced::Result { - env_logger::builder().format_timestamp(None).init(); + tracing_subscriber::fmt::init(); GameOfLife::run(Settings { antialiasing: true, diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml index 6068d651..9606dcb3 100644 --- a/examples/geometry/Cargo.toml +++ b/examples/geometry/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["advanced"] } +iced.workspace = true +iced.features = ["advanced"] diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml index 22914742..4c55daa7 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -6,18 +6,19 @@ edition = "2021" publish = false [dependencies] -iced_winit = { path = "../../winit" } -iced_wgpu = { path = "../../wgpu" } -iced_widget = { path = "../../widget" } -iced_renderer = { path = "../../renderer", features = ["wgpu"] } -env_logger = "0.10" +iced_winit.workspace = true +iced_wgpu.workspace = true +iced_widget.workspace = true + +tracing-subscriber = "0.3" [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = "0.1.7" console_log = "0.2.0" -log = "0.4" +log.workspace = true wasm-bindgen = "0.2" web-sys = { version = "0.3", features = ["Element", "HtmlCanvasElement", "Window", "Document"] } + # This dependency a little bit quirky, it is deep in the tree and without `js` feature it # refuses to work with `wasm32-unknown-unknown target`. Unfortunately, we need this patch # to make it work diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 98b58f16..af48af5f 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -41,7 +41,7 @@ pub fn main() -> Result<(), Box> { }; #[cfg(not(target_arch = "wasm32"))] - env_logger::init(); + tracing_subscriber::fmt::init(); // Initialize winit let event_loop = EventLoop::new(); diff --git a/examples/lazy/Cargo.toml b/examples/lazy/Cargo.toml index e03e89a9..4ccb9584 100644 --- a/examples/lazy/Cargo.toml +++ b/examples/lazy/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug", "lazy"] } +iced.workspace = true +iced.features = ["debug", "lazy"] diff --git a/examples/loading_spinners/Cargo.toml b/examples/loading_spinners/Cargo.toml index ee9a48aa..a32da386 100644 --- a/examples/loading_spinners/Cargo.toml +++ b/examples/loading_spinners/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["advanced", "canvas"] } -lyon_algorithms = "1" -once_cell = "1" +iced.workspace = true +iced.features = ["advanced", "canvas"] + +lyon_algorithms = "1.0" +once_cell.workspace = true \ No newline at end of file diff --git a/examples/modal/Cargo.toml b/examples/modal/Cargo.toml index 3ac61e6a..009d9653 100644 --- a/examples/modal/Cargo.toml +++ b/examples/modal/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["advanced"] } +iced.workspace = true +iced.features = ["advanced"] diff --git a/examples/multitouch/Cargo.toml b/examples/multitouch/Cargo.toml index 867312f8..e0d14f58 100644 --- a/examples/multitouch/Cargo.toml +++ b/examples/multitouch/Cargo.toml @@ -6,7 +6,8 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["canvas", "tokio", "debug"] } -tokio = { version = "1.0", features = ["sync"] } -env_logger = "0.10" +iced.workspace = true +iced.features = ["debug", "canvas", "tokio"] + +tracing-subscriber = "0.3" voronator = "0.2" diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index 2830b78d..ba8df7aa 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -13,7 +13,7 @@ use iced::{ use std::collections::HashMap; pub fn main() -> iced::Result { - env_logger::builder().format_timestamp(None).init(); + tracing_subscriber::fmt::init(); Multitouch::run(Settings { antialiasing: true, diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index 4c0bf072..095ecd10 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug", "lazy"] } +iced.workspace = true +iced.features = ["debug", "lazy"] diff --git a/examples/pick_list/Cargo.toml b/examples/pick_list/Cargo.toml index 4aa4603a..030558e7 100644 --- a/examples/pick_list/Cargo.toml +++ b/examples/pick_list/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug"] } +iced.workspace = true +iced.features = ["debug"] diff --git a/examples/pokedex/Cargo.toml b/examples/pokedex/Cargo.toml index e99fc0c3..de8a5c17 100644 --- a/examples/pokedex/Cargo.toml +++ b/examples/pokedex/Cargo.toml @@ -6,7 +6,9 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["image", "debug", "tokio"] } +iced.workspace = true +iced.features = ["image", "debug", "tokio"] + serde_json = "1.0" [dependencies.serde] diff --git a/examples/progress_bar/Cargo.toml b/examples/progress_bar/Cargo.toml index 383a9bdd..6624ae15 100644 --- a/examples/progress_bar/Cargo.toml +++ b/examples/progress_bar/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } +iced.workspace = true diff --git a/examples/qr_code/Cargo.toml b/examples/qr_code/Cargo.toml index 2f164df6..8f33ea8c 100644 --- a/examples/qr_code/Cargo.toml +++ b/examples/qr_code/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["qr_code"] } +iced.workspace = true +iced.features = ["qr_code"] diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index b79300b7..dcd77439 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug", "image", "advanced"] } -image = { version = "0.24.6", features = ["png"]} -env_logger = "0.10.0" +iced.workspace = true +iced.features = ["debug", "image", "advanced"] + +image = { workspace = true, features = ["png"]} +tracing-subscriber = "0.3" \ No newline at end of file diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 83824535..1d8eaa44 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -13,7 +13,7 @@ use ::image as img; use ::image::ColorType; fn main() -> iced::Result { - env_logger::builder().format_timestamp(None).init(); + tracing_subscriber::fmt::init(); Example::run(iced::Settings::default()) } diff --git a/examples/scrollable/Cargo.toml b/examples/scrollable/Cargo.toml index e6411e26..f8c735c0 100644 --- a/examples/scrollable/Cargo.toml +++ b/examples/scrollable/Cargo.toml @@ -6,5 +6,7 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug"] } -once_cell = "1.16.0" +iced.workspace = true +iced.features = ["debug"] + +once_cell.workspace = true diff --git a/examples/sierpinski_triangle/Cargo.toml b/examples/sierpinski_triangle/Cargo.toml index 39d45f64..600a9e06 100644 --- a/examples/sierpinski_triangle/Cargo.toml +++ b/examples/sierpinski_triangle/Cargo.toml @@ -6,5 +6,7 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../..", features = ["canvas", "debug"] } -rand = "0.8.4" +iced.workspace = true +iced.features = ["debug", "canvas"] + +rand = "0.8" diff --git a/examples/slider/Cargo.toml b/examples/slider/Cargo.toml index 112d7cff..fad8916e 100644 --- a/examples/slider/Cargo.toml +++ b/examples/slider/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } +iced.workspace = true diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml index 1a98a87e..ca64da14 100644 --- a/examples/solar_system/Cargo.toml +++ b/examples/solar_system/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["canvas", "tokio", "debug"] } -env_logger = "0.10.0" +iced.workspace = true +iced.features = ["debug", "canvas", "tokio"] + rand = "0.8.3" +tracing-subscriber = "0.3" diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 58d06206..8fa8946e 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -23,7 +23,7 @@ use iced::{ use std::time::Instant; pub fn main() -> iced::Result { - env_logger::builder().format_timestamp(None).init(); + tracing_subscriber::fmt::init(); SolarSystem::run(Settings { antialiasing: true, diff --git a/examples/stopwatch/Cargo.toml b/examples/stopwatch/Cargo.toml index f623feb9..6b1419f6 100644 --- a/examples/stopwatch/Cargo.toml +++ b/examples/stopwatch/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["smol"] } +iced.workspace = true +iced.features = ["smol"] diff --git a/examples/styling/Cargo.toml b/examples/styling/Cargo.toml index f771708c..c8a90258 100644 --- a/examples/styling/Cargo.toml +++ b/examples/styling/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } +iced.workspace = true diff --git a/examples/svg/Cargo.toml b/examples/svg/Cargo.toml index f5a6eaa2..78208fb0 100644 --- a/examples/svg/Cargo.toml +++ b/examples/svg/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["svg"] } +iced.workspace = true +iced.features = ["svg"] diff --git a/examples/system_information/Cargo.toml b/examples/system_information/Cargo.toml index 7d1e4b94..41903122 100644 --- a/examples/system_information/Cargo.toml +++ b/examples/system_information/Cargo.toml @@ -6,5 +6,7 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["system"] } -bytesize = { version = "1.1.0" } +iced.workspace = true +iced.features = ["system"] + +bytesize = "1.1" diff --git a/examples/toast/Cargo.toml b/examples/toast/Cargo.toml index f703572c..113313e2 100644 --- a/examples/toast/Cargo.toml +++ b/examples/toast/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["advanced"] } +iced.workspace = true +iced.features = ["advanced"] diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index 7ad4d558..fea20375 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -6,18 +6,20 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["async-std", "debug"] } +iced.workspace = true +iced.features = ["async-std", "debug"] + serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -once_cell = "1.15" +once_cell.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -async-std = "1.0" +async-std.workspace = true directories-next = "2.0" [target.'cfg(target_arch = "wasm32")'.dependencies] -web-sys = { version = "0.3", features = ["Window", "Storage"] } -wasm-timer = "0.2" +web-sys = { workspace = true, features = ["Window", "Storage"] } +wasm-timer.workspace = true [package.metadata.deb] assets = [ diff --git a/examples/tooltip/Cargo.toml b/examples/tooltip/Cargo.toml index 25840fb4..57bb0dcb 100644 --- a/examples/tooltip/Cargo.toml +++ b/examples/tooltip/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug"] } +iced.workspace = true +iced.features = ["debug"] diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index 48471f2d..21206ecb 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -6,5 +6,7 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["image", "debug"] } -env_logger = "0.10.0" +iced.workspace = true +iced.features = ["image", "debug"] + +tracing-subscriber = "0.3" diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 13bcd5ff..af508206 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -8,7 +8,7 @@ use iced::widget::{Button, Column, Container, Slider}; use iced::{Color, Element, Font, Length, Renderer, Sandbox, Settings}; pub fn main() -> iced::Result { - env_logger::init(); + tracing_subscriber::fmt::init(); Tour::run(Settings::default()) } diff --git a/examples/url_handler/Cargo.toml b/examples/url_handler/Cargo.toml index 4dcff92d..7bb9914b 100644 --- a/examples/url_handler/Cargo.toml +++ b/examples/url_handler/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } +iced.workspace = true diff --git a/examples/visible_bounds/Cargo.toml b/examples/visible_bounds/Cargo.toml index cfa56dd2..37594b84 100644 --- a/examples/visible_bounds/Cargo.toml +++ b/examples/visible_bounds/Cargo.toml @@ -6,5 +6,7 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug"] } -once_cell = "1" +iced.workspace = true +iced.features = ["debug"] + +once_cell.workspace = true diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index 12af9658..2756e8e0 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -6,16 +6,16 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["tokio", "debug"] } -once_cell = "1.15" +iced.workspace = true +iced.features = ["debug", "tokio"] + +once_cell.workspace = true +warp = "0.3" [dependencies.async-tungstenite] version = "0.23" features = ["tokio-rustls-webpki-roots"] [dependencies.tokio] -version = "1" +workspace = true features = ["time"] - -[dependencies.warp] -version = "0.3" diff --git a/futures/Cargo.toml b/futures/Cargo.toml index 044827c2..69a915e4 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -1,47 +1,40 @@ [package] name = "iced_futures" -version = "0.7.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -description = "Commands, subscriptions, and runtimes for Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced_futures" -keywords = ["gui", "ui", "graphics", "interface", "futures"] -categories = ["gui"] +description = "Commands, subscriptions, and future executors for iced" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +all-features = true [features] thread-pool = ["futures/thread-pool"] [dependencies] -log = "0.4" - -[dependencies.iced_core] -version = "0.10" -path = "../core" +iced_core.workspace = true -[dependencies.futures] -version = "0.3" +futures.workspace = true +log.workspace = true -[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio] -package = "tokio" -version = "1.0" -optional = true -features = ["rt", "rt-multi-thread", "time"] +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +async-std.workspace = true +async-std.optional = true +async-std.features = ["unstable"] -[target.'cfg(not(target_arch = "wasm32"))'.dependencies.async-std] -version = "1.0" -optional = true -features = ["unstable"] +smol.workspace = true +smol.optional = true -[target.'cfg(not(target_arch = "wasm32"))'.dependencies.smol] -version = "1.2" -optional = true +tokio.workspace = true +tokio.optional = true +tokio.features = ["rt", "rt-multi-thread", "time"] [target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen-futures = "0.4" -wasm-timer = "0.2" - -[package.metadata.docs.rs] -rustdoc-args = ["--cfg", "docsrs"] -all-features = true +wasm-bindgen-futures.workspace = true +wasm-timer.workspace = true diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index ca7bf61a..23a19845 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -1,14 +1,18 @@ [package] name = "iced_graphics" -version = "0.9.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced_graphics" -keywords = ["gui", "ui", "graphics", "interface", "widgets"] -categories = ["gui"] +description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for iced" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +all-features = true [features] geometry = ["lyon_path"] @@ -17,33 +21,21 @@ image = ["dep:image", "kamadak-exif"] web-colors = [] [dependencies] -glam = "0.24" -half = "2.2.1" -log = "0.4" -raw-window-handle = "0.5" -thiserror = "1.0" -bitflags = "1.2" - -[dependencies.bytemuck] -version = "1.4" -features = ["derive"] +iced_core.workspace = true -[dependencies.iced_core] -version = "0.10" -path = "../core" +bitflags.workspace = true +bytemuck.workspace = true +glam.workspace = true +half.workspace = true +log.workspace = true +raw-window-handle.workspace = true +thiserror.workspace = true -[dependencies.image] -version = "0.24" -optional = true +lyon_path.workspace = true +lyon_path.optional = true -[dependencies.kamadak-exif] -version = "0.5" -optional = true +image.workspace = true +image.optional = true -[dependencies.lyon_path] -version = "1" -optional = true - -[package.metadata.docs.rs] -rustdoc-args = ["--cfg", "docsrs"] -all-features = true +kamadak-exif.workspace = true +kamadak-exif.optional = true diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 2c88cf31..56e17209 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "iced_renderer" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -description = "The official renderer for Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced_renderer" -keywords = ["gui", "ui", "graphics", "interface", "widgets"] -categories = ["gui"] +description = "The official renderer for iced" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true [features] wgpu = ["iced_wgpu"] @@ -20,19 +20,12 @@ web-colors = ["iced_wgpu?/web-colors"] webgl = ["iced_wgpu?/webgl"] [dependencies] -raw-window-handle = "0.5" -thiserror = "1" -log = "0.4" +iced_graphics.workspace = true +iced_tiny_skia.workspace = true -[dependencies.iced_graphics] -version = "0.9" -path = "../graphics" +iced_wgpu.workspace = true +iced_wgpu.optional = true -[dependencies.iced_tiny_skia] -version = "0.1" -path = "../tiny_skia" - -[dependencies.iced_wgpu] -version = "0.11" -path = "../wgpu" -optional = true +log.workspace = true +raw-window-handle.workspace = true +thiserror.workspace = true diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 9fbe4be7..d19aedd3 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -1,23 +1,21 @@ [package] name = "iced_runtime" -version = "0.1.1" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -description = "A renderer-agnostic runtime for Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" +description = "A renderer-agnostic runtime for iced" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true [features] debug = [] [dependencies] -thiserror = "1" +iced_core.workspace = true +iced_futures.workspace = true +iced_futures.features = ["thread-pool"] -[dependencies.iced_core] -version = "0.10" -path = "../core" - -[dependencies.iced_futures] -version = "0.7" -path = "../futures" -features = ["thread-pool"] +thiserror.workspace = true diff --git a/src/window/icon.rs b/src/window/icon.rs index 0fe010ca..0cb206b3 100644 --- a/src/window/icon.rs +++ b/src/window/icon.rs @@ -13,7 +13,7 @@ use std::path::Path; /// This will return an error in case the file is missing at run-time. You may prefer [`Self::from_file_data`] instead. #[cfg(feature = "image")] pub fn from_file>(icon_path: P) -> Result { - let icon = image_rs::io::Reader::open(icon_path)?.decode()?.to_rgba8(); + let icon = image::io::Reader::open(icon_path)?.decode()?.to_rgba8(); Ok(icon::from_rgba(icon.to_vec(), icon.width(), icon.height())?) } @@ -25,9 +25,10 @@ pub fn from_file>(icon_path: P) -> Result { #[cfg(feature = "image")] pub fn from_file_data( data: &[u8], - explicit_format: Option, + explicit_format: Option, ) -> Result { - let mut icon = image_rs::io::Reader::new(std::io::Cursor::new(data)); + let mut icon = image::io::Reader::new(std::io::Cursor::new(data)); + let icon_with_format = match explicit_format { Some(format) => { icon.set_format(format); @@ -59,5 +60,5 @@ pub enum Error { /// The `image` crate reported an error. #[cfg(feature = "image")] #[error("Unable to create icon from a file: {0}")] - ImageError(#[from] image_rs::error::ImageError), + ImageError(#[from] image::error::ImageError), } diff --git a/style/Cargo.toml b/style/Cargo.toml index 689cf978..3f00e787 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -1,22 +1,18 @@ [package] name = "iced_style" -version = "0.9.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" description = "The default set of styles of Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced_style" -keywords = ["gui", "ui", "graphics", "interface", "widgets"] -categories = ["gui"] +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true -[dependencies.iced_core] -version = "0.10" -path = "../core" -features = ["palette"] +[dependencies] +iced_core.workspace = true +iced_core.features = ["palette"] -[dependencies.palette] -version = "0.7" - -[dependencies.once_cell] -version = "1.15" +palette.workspace = true +once_cell.workspace = true diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 9aa63a4f..15a6928a 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "iced_tiny_skia" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -description = "A software renderer for Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced_tiny_skia" -keywords = ["gui", "ui", "graphics", "interface", "widgets"] -categories = ["gui"] +description = "A software renderer for iced on top of tiny-skia" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true [features] image = ["iced_graphics/image"] @@ -16,27 +16,21 @@ svg = ["resvg"] geometry = ["iced_graphics/geometry"] [dependencies] -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" -log = "0.4" +iced_graphics.workspace = true -[dependencies.iced_graphics] -version = "0.9" -path = "../graphics" +bytemuck.workspace = true +cosmic-text.workspace = true +kurbo.workspace = true +log.workspace = true +raw-window-handle.workspace = true +rustc-hash.workspace = true +softbuffer.workspace = true +tiny-skia.workspace = true +twox-hash.workspace = true -[dependencies.twox-hash] -version = "1.6" -default-features = false +resvg.workspace = true +resvg.optional = true -[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash] -version = "1.6.1" -features = ["std"] - -[dependencies.resvg] -version = "0.35" -optional = true +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +twox-hash.workspace = true +twox-hash.features = ["std"] diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 49c62673..97594f1a 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -1,11 +1,18 @@ [package] name = "iced_wgpu" -version = "0.11.1" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -description = "A wgpu renderer for Iced" -license = "MIT AND OFL-1.1" -repository = "https://github.com/iced-rs/iced" +description = "A renderer for iced on top of wgpu" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +all-features = true [features] geometry = ["iced_graphics/geometry", "lyon"] @@ -15,47 +22,30 @@ web-colors = ["iced_graphics/web-colors"] webgl = ["wgpu/webgl"] [dependencies] -wgpu = "0.17" -glyphon = { git = "https://github.com/grovesNL/glyphon.git", rev = "20f0f8fa80e0d0df4c63634ce9176fa489546ca9" } -raw-window-handle = "0.5" -guillotiere = "0.6" -futures = "0.3" -bitflags = "1.2" -once_cell = "1.0" -rustc-hash = "1.1" -log = "0.4" - -[dependencies.twox-hash] -version = "1.6" -default-features = false - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash] -version = "1.6.1" -features = ["std"] - -[dependencies.bytemuck] -version = "1.9" -features = ["derive"] - -[dependencies.iced_graphics] -version = "0.9" -path = "../graphics" - -[dependencies.glam] -version = "0.24" - -[dependencies.lyon] -version = "1.0" -optional = true - -[dependencies.resvg] -version = "0.35" -optional = true - -[dependencies.tracing] -version = "0.1.6" -optional = true - -[package.metadata.docs.rs] -rustdoc-args = ["--cfg", "docsrs"] -all-features = true +iced_graphics.workspace = true + +bitflags.workspace = true +bytemuck.workspace = true +futures.workspace = true +glam.workspace = true +glyphon.workspace = true +guillotiere.workspace = true +log.workspace = true +once_cell.workspace = true +raw-window-handle.workspace = true +rustc-hash.workspace = true +twox-hash.workspace = true +wgpu.workspace = true + +lyon.workspace = true +lyon.optional = true + +resvg.workspace = true +resvg.optional = true + +tracing.workspace = true +tracing.optional = true + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +twox-hash.workspace = true +twox-hash.features = ["std"] diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 128a7c38..6d62c181 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -1,14 +1,18 @@ [package] name = "iced_widget" -version = "0.1.3" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -description = "The built-in widgets for Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced_widget" -keywords = ["gui", "ui", "graphics", "interface", "widgets"] -categories = ["gui"] +description = "The built-in widgets for iced" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +all-features = true [features] lazy = ["ouroboros"] @@ -18,31 +22,16 @@ canvas = ["iced_renderer/geometry"] qr_code = ["canvas", "qrcode"] [dependencies] -unicode-segmentation = "1.6" -num-traits = "0.2" -thiserror = "1" - -[dependencies.iced_runtime] -version = "0.1" -path = "../runtime" +iced_renderer.workspace = true +iced_runtime.workspace = true +iced_style.workspace = true -[dependencies.iced_renderer] -version = "0.1" -path = "../renderer" +num-traits.workspace = true +thiserror.workspace = true +unicode-segmentation.workspace = true -[dependencies.iced_style] -version = "0.9" -path = "../style" +ouroboros.workspace = true +ouroboros.optional = true -[dependencies.ouroboros] -version = "0.17" -optional = true - -[dependencies.qrcode] -version = "0.12" -optional = true -default-features = false - -[package.metadata.docs.rs] -rustdoc-args = ["--cfg", "docsrs"] -all-features = true +qrcode.workspace = true +qrcode.optional = true diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 67216147..674a66d3 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -1,19 +1,17 @@ [package] name = "iced_winit" -version = "0.10.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -description = "A winit runtime for Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced_winit" -keywords = ["gui", "ui", "graphics", "interface", "widgets"] -categories = ["gui"] +description = "A runtime for iced on top of winit" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true [features] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] -trace = ["tracing", "tracing-core", "tracing-subscriber"] -chrome-trace = ["trace", "tracing-chrome"] debug = ["iced_runtime/debug"] system = ["sysinfo"] application = [] @@ -23,54 +21,23 @@ wayland-dlopen = ["winit/wayland-dlopen"] wayland-csd-adwaita = ["winit/wayland-csd-adwaita"] [dependencies] -window_clipboard = "0.3" -log = "0.4" -thiserror = "1.0" -raw-window-handle = "0.5" - -[dependencies.winit] -version = "0.28" -git = "https://github.com/iced-rs/winit.git" -rev = "c52db2045d0a2f1b8d9923870de1d4ab1994146e" -default-features = false - -[dependencies.iced_runtime] -version = "0.1" -path = "../runtime" - -[dependencies.iced_graphics] -version = "0.9" -path = "../graphics" - -[dependencies.iced_style] -version = "0.9" -path = "../style" - -[dependencies.tracing] -version = "0.1.37" -optional = true -features = ["std"] - -[dependencies.tracing-core] -version = "0.1.30" -optional = true - -[dependencies.tracing-subscriber] -version = "0.3.16" -optional = true -features = ["registry"] - -[dependencies.tracing-chrome] -version = "0.7.0" -optional = true - -[target.'cfg(target_os = "windows")'.dependencies.winapi] -version = "0.3.6" - -[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] -version = "0.3" -features = ["Document", "Window"] - -[dependencies.sysinfo] -version = "0.28" -optional = true +iced_graphics.workspace = true +iced_runtime.workspace = true +iced_style.workspace = true + +log.workspace = true +raw-window-handle.workspace = true +thiserror.workspace = true +tracing.workspace = true +window_clipboard.workspace = true +winit.workspace = true + +sysinfo.workspace = true +sysinfo.optional = true + +[target.'cfg(target_os = "windows")'.dependencies] +winapi.workspace = true + +[target.'cfg(target_arch = "wasm32")'.dependencies] +web-sys.workspace = true +web-sys.features = ["Document", "Window"] -- cgit From 34876aea5ea698f342220e7b7c0bed33a42859d2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 4 Sep 2023 13:32:27 +0200 Subject: Add GitHub CI workflow to detect outdated dependencies --- .github/workflows/detect.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/detect.yml diff --git a/.github/workflows/detect.yml b/.github/workflows/detect.yml new file mode 100644 index 00000000..c4b7413d --- /dev/null +++ b/.github/workflows/detect.yml @@ -0,0 +1,16 @@ +name: Detect +on: + push: {} + pull_request: {} + schedule: + - cron: '0 0 * * *' +jobs: + outdated_dependencies: + runs-on: ubuntu-latest + steps: + - uses: hecrj/setup-rust-action@v1 + - name: Install cargo-outdated + run: cargo install cargo-outdated + - uses: actions/checkout@master + - name: Detect outdated dependencies + run: cargo outdated --exit-code 1 -- cgit From 20681b4777a1954e6b7f659d5bc1817f7924f40d Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Mon, 4 Sep 2023 21:03:16 -0400 Subject: Ensure LineHeight is always > 0.0 for tiny skia. --- tiny_skia/src/text.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 08fde4bf..9b949218 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -54,7 +54,8 @@ impl Pipeline { pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::Mask>, ) { - let line_height = f32::from(line_height.to_absolute(Pixels(size))); + let line_height = f32::from(line_height.to_absolute(Pixels(size))) + .max(f32::MIN_POSITIVE); let font_system = self.font_system.get_mut(); let key = Key { @@ -134,7 +135,8 @@ impl Pipeline { ) -> Size { let mut measurement_cache = self.cache.borrow_mut(); - let line_height = f32::from(line_height.to_absolute(Pixels(size))); + let line_height = f32::from(line_height.to_absolute(Pixels(size))) + .max(f32::MIN_POSITIVE); let (_, entry) = measurement_cache.allocate( &mut self.font_system.borrow_mut(), @@ -164,7 +166,8 @@ impl Pipeline { ) -> Option { let mut measurement_cache = self.cache.borrow_mut(); - let line_height = f32::from(line_height.to_absolute(Pixels(size))); + let line_height = f32::from(line_height.to_absolute(Pixels(size))) + .max(f32::MIN_POSITIVE); let (_, entry) = measurement_cache.allocate( &mut self.font_system.borrow_mut(), @@ -405,7 +408,10 @@ impl Cache { } if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { - let metrics = cosmic_text::Metrics::new(key.size, key.size * 1.2); + let metrics = cosmic_text::Metrics::new( + key.size, + (key.size * 1.2).max(f32::MIN_POSITIVE), + ); let mut buffer = cosmic_text::Buffer::new(font_system, metrics); buffer.set_size( -- cgit From bdf18554feadb631fdae5b427ac7a92a5546ade1 Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Mon, 4 Sep 2023 23:47:44 -0400 Subject: Check LineHeight > 0.0 before allocating text --- tiny_skia/src/text.rs | 13 +++++-------- wgpu/src/text.rs | 14 +++++++------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 9b949218..24b17662 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -54,8 +54,7 @@ impl Pipeline { pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::Mask>, ) { - let line_height = f32::from(line_height.to_absolute(Pixels(size))) - .max(f32::MIN_POSITIVE); + let line_height = f32::from(line_height.to_absolute(Pixels(size))); let font_system = self.font_system.get_mut(); let key = Key { @@ -135,8 +134,7 @@ impl Pipeline { ) -> Size { let mut measurement_cache = self.cache.borrow_mut(); - let line_height = f32::from(line_height.to_absolute(Pixels(size))) - .max(f32::MIN_POSITIVE); + let line_height = f32::from(line_height.to_absolute(Pixels(size))); let (_, entry) = measurement_cache.allocate( &mut self.font_system.borrow_mut(), @@ -166,8 +164,7 @@ impl Pipeline { ) -> Option { let mut measurement_cache = self.cache.borrow_mut(); - let line_height = f32::from(line_height.to_absolute(Pixels(size))) - .max(f32::MIN_POSITIVE); + let line_height = f32::from(line_height.to_absolute(Pixels(size))); let (_, entry) = measurement_cache.allocate( &mut self.font_system.borrow_mut(), @@ -409,8 +406,8 @@ impl Cache { if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { let metrics = cosmic_text::Metrics::new( - key.size, - (key.size * 1.2).max(f32::MIN_POSITIVE), + key.line_height, + (key.line_height * 1.2).max(f32::MIN_POSITIVE), ); let mut buffer = cosmic_text::Buffer::new(font_system, metrics); diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 08a32b5e..9c42be0e 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -96,8 +96,7 @@ impl Pipeline { section .line_height .to_absolute(Pixels(section.size)), - ) - .max(f32::MIN_POSITIVE), + ), font: section.font, bounds: Size { width: section.bounds.width, @@ -239,8 +238,7 @@ impl Pipeline { ) -> Size { let mut cache = self.cache.borrow_mut(); - let line_height = f32::from(line_height.to_absolute(Pixels(size))) - .max(f32::MIN_POSITIVE); + let line_height = f32::from(line_height.to_absolute(Pixels(size))); let (_, entry) = cache.allocate( &mut self.font_system.borrow_mut(), @@ -271,8 +269,7 @@ impl Pipeline { ) -> Option { let mut cache = self.cache.borrow_mut(); - let line_height = f32::from(line_height.to_absolute(Pixels(size))) - .max(f32::MIN_POSITIVE); + let line_height = f32::from(line_height.to_absolute(Pixels(size))); let (_, entry) = cache.allocate( &mut self.font_system.borrow_mut(), @@ -417,7 +414,10 @@ impl Cache { } if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { - let metrics = glyphon::Metrics::new(key.size, key.line_height); + let metrics = glyphon::Metrics::new( + key.size, + key.line_height.max(f32::MIN_POSITIVE), + ); let mut buffer = glyphon::Buffer::new(font_system, metrics); buffer.set_size( -- cgit From 5371fae21a4c1110a37e7183e794cba234598d9c Mon Sep 17 00:00:00 2001 From: ripytide Date: Tue, 5 Sep 2023 10:49:50 +0100 Subject: added a Frame::scale_nonuniform method --- renderer/src/geometry.rs | 8 +++++++- tiny_skia/src/geometry.rs | 4 ++++ wgpu/src/geometry.rs | 10 +++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs index 04b5d9e6..0e524169 100644 --- a/renderer/src/geometry.rs +++ b/renderer/src/geometry.rs @@ -168,12 +168,18 @@ impl Frame { delegate!(self, frame, frame.rotate(angle)); } - /// Applies a scaling to the current transform of the [`Frame`]. + /// Applies a uniform scaling to the current transform of the [`Frame`]. #[inline] pub fn scale(&mut self, scale: f32) { delegate!(self, frame, frame.scale(scale)); } + /// Applies a non-uniform scaling to the current transform of the [`Frame`]. + #[inline] + pub fn scale_nonuniform(&mut self, scale: Vector) { + delegate!(self, frame, frame.scale_nonuniform(scale)); + } + pub fn into_geometry(self) -> Geometry { match self { Self::TinySkia(frame) => Geometry::TinySkia(frame.into_primitive()), diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 0fae7364..a416020f 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -158,6 +158,10 @@ impl Frame { self.transform = self.transform.pre_scale(scale, scale); } + pub fn scale_nonuniform(&mut self, scale: Vector) { + self.transform = self.transform.pre_scale(scale.x, scale.y); + } + pub fn into_primitive(self) -> Primitive { Primitive::Clip { bounds: Rectangle::new(Point::ORIGIN, self.size), diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index e421e0b0..2cd07a27 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -444,7 +444,7 @@ impl Frame { self.transforms.current.is_identity = false; } - /// Applies a scaling to the current transform of the [`Frame`]. + /// Applies a uniform scaling to the current transform of the [`Frame`]. #[inline] pub fn scale(&mut self, scale: f32) { self.transforms.current.raw = @@ -452,6 +452,14 @@ impl Frame { self.transforms.current.is_identity = false; } + /// Applies a non-uniform scaling to the current transform of the [`Frame`]. + #[inline] + pub fn scale_nonuniform(&mut self, scale: Vector) { + self.transforms.current.raw = + self.transforms.current.raw.pre_scale(scale.x, scale.y); + self.transforms.current.is_identity = false; + } + /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. pub fn into_primitive(self) -> Primitive { Primitive::Group { -- cgit From cee0ed64694e06eb3061acc1abd76deded3e0648 Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Tue, 5 Sep 2023 20:45:27 -0400 Subject: Use the correct text size and height in tiny_skia --- tiny_skia/src/text.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 24b17662..306b400a 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -406,8 +406,8 @@ impl Cache { if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { let metrics = cosmic_text::Metrics::new( - key.line_height, - (key.line_height * 1.2).max(f32::MIN_POSITIVE), + key.size, + key.line_height.max(f32::MIN_POSITIVE), ); let mut buffer = cosmic_text::Buffer::new(font_system, metrics); -- cgit From 08a031cbe5913c249efa7fc82556d5d95f981c4c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 02:45:15 +0200 Subject: Introduce `keyboard::on_key_press` and `on_key_release` Also rename `subscription::events*` to `event::listen*`. --- examples/events/src/main.rs | 5 +- examples/modal/src/main.rs | 8 ++-- examples/pane_grid/src/main.rs | 14 ++---- examples/screenshot/src/main.rs | 7 ++- examples/toast/src/main.rs | 8 ++-- examples/todos/src/main.rs | 40 +++++----------- examples/url_handler/src/main.rs | 13 +++--- examples/visible_bounds/src/main.rs | 8 ++-- futures/src/event.rs | 59 ++++++++++++++++++++++++ futures/src/keyboard.rs | 61 +++++++++++++++++++++++++ futures/src/lib.rs | 2 + futures/src/subscription.rs | 91 ++++++++----------------------------- runtime/src/window.rs | 5 +- src/lib.rs | 10 +++- 14 files changed, 193 insertions(+), 138 deletions(-) create mode 100644 futures/src/event.rs create mode 100644 futures/src/keyboard.rs diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 7f3a5e1d..32d0da2c 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,9 +1,8 @@ use iced::alignment; +use iced::event::{self, Event}; use iced::executor; -use iced::subscription; use iced::widget::{button, checkbox, container, text, Column}; use iced::window; -use iced::Event; use iced::{ Alignment, Application, Command, Element, Length, Settings, Subscription, Theme, @@ -71,7 +70,7 @@ impl Application for Events { } fn subscription(&self) -> Subscription { - subscription::events().map(Message::EventOccurred) + event::listen().map(Message::EventOccurred) } fn view(&self) -> Element { diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index 8a48f830..4aa70886 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -1,12 +1,14 @@ +use iced::event::{self, Event}; use iced::executor; use iced::keyboard; -use iced::subscription::{self, Subscription}; use iced::theme; use iced::widget::{ self, button, column, container, horizontal_space, pick_list, row, text, text_input, }; -use iced::{Alignment, Application, Command, Element, Event, Length, Settings}; +use iced::{ + Alignment, Application, Command, Element, Length, Settings, Subscription, +}; use modal::Modal; use std::fmt; @@ -49,7 +51,7 @@ impl Application for App { } fn subscription(&self) -> Subscription { - subscription::events().map(Message::Event) + event::listen().map(Message::Event) } fn update(&mut self, message: Message) -> Command { diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 04896e20..af87e2c0 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,8 +1,6 @@ use iced::alignment::{self, Alignment}; -use iced::event::{self, Event}; use iced::executor; use iced::keyboard; -use iced::subscription; use iced::theme::{self, Theme}; use iced::widget::pane_grid::{self, PaneGrid}; use iced::widget::{ @@ -146,18 +144,12 @@ impl Application for Example { } fn subscription(&self) -> Subscription { - subscription::events_with(|event, status| { - if let event::Status::Captured = status { + keyboard::on_key_press(|key_code, modifiers| { + if !modifiers.command() { return None; } - match event { - Event::Keyboard(keyboard::Event::KeyPressed { - modifiers, - key_code, - }) if modifiers.command() => handle_hotkey(key_code), - _ => None, - } + handle_hotkey(key_code) }) } diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 83824535..54446724 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -4,9 +4,8 @@ use iced::theme::{Button, Container}; use iced::widget::{button, column, container, image, row, text, text_input}; use iced::window::screenshot::{self, Screenshot}; use iced::{ - event, executor, keyboard, subscription, Alignment, Application, Command, - ContentFit, Element, Event, Length, Rectangle, Renderer, Subscription, - Theme, + event, executor, keyboard, Alignment, Application, Command, ContentFit, + Element, Event, Length, Rectangle, Renderer, Subscription, Theme, }; use ::image as img; @@ -254,7 +253,7 @@ impl Application for Example { } fn subscription(&self) -> Subscription { - subscription::events_with(|event, status| { + event::listen_with(|event, status| { if let event::Status::Captured = status { return None; } diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 42f6c348..47b272a9 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -1,10 +1,12 @@ +use iced::event::{self, Event}; use iced::executor; use iced::keyboard; -use iced::subscription::{self, Subscription}; use iced::widget::{ self, button, column, container, pick_list, row, slider, text, text_input, }; -use iced::{Alignment, Application, Command, Element, Event, Length, Settings}; +use iced::{ + Alignment, Application, Command, Element, Length, Settings, Subscription, +}; use toast::{Status, Toast}; @@ -57,7 +59,7 @@ impl Application for App { } fn subscription(&self) -> Subscription { - subscription::events().map(Message::Event) + event::listen().map(Message::Event) } fn update(&mut self, message: Message) -> Command { diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 6ad7b4fb..62c17926 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,8 +1,6 @@ use iced::alignment::{self, Alignment}; -use iced::event::{self, Event}; use iced::font::{self, Font}; -use iced::keyboard::{self, KeyCode, Modifiers}; -use iced::subscription; +use iced::keyboard; use iced::theme::{self, Theme}; use iced::widget::{ self, button, checkbox, column, container, row, scrollable, text, @@ -52,7 +50,7 @@ enum Message { FilterChanged(Filter), TaskMessage(usize, TaskMessage), TabPressed { shift: bool }, - ToggleFullscreen(window::Mode), + ChangeWindowMode(window::Mode), } impl Application for Todos { @@ -163,7 +161,7 @@ impl Application for Todos { widget::focus_next() } } - Message::ToggleFullscreen(mode) => { + Message::ChangeWindowMode(mode) => { window::change_mode(mode) } _ => Command::none(), @@ -262,33 +260,19 @@ impl Application for Todos { } fn subscription(&self) -> Subscription { - subscription::events_with(|event, status| match (event, status) { - ( - Event::Keyboard(keyboard::Event::KeyPressed { - key_code: keyboard::KeyCode::Tab, - modifiers, - .. + keyboard::on_key_press(|key_code, modifiers| { + match (key_code, modifiers) { + (keyboard::KeyCode::Tab, _) => Some(Message::TabPressed { + shift: modifiers.shift(), }), - event::Status::Ignored, - ) => Some(Message::TabPressed { - shift: modifiers.shift(), - }), - ( - Event::Keyboard(keyboard::Event::KeyPressed { - key_code, - modifiers: Modifiers::SHIFT, - }), - event::Status::Ignored, - ) => match key_code { - KeyCode::Up => { - Some(Message::ToggleFullscreen(window::Mode::Fullscreen)) + (keyboard::KeyCode::Up, keyboard::Modifiers::SHIFT) => { + Some(Message::ChangeWindowMode(window::Mode::Fullscreen)) } - KeyCode::Down => { - Some(Message::ToggleFullscreen(window::Mode::Windowed)) + (keyboard::KeyCode::Down, keyboard::Modifiers::SHIFT) => { + Some(Message::ChangeWindowMode(window::Mode::Windowed)) } _ => None, - }, - _ => None, + } }) } } diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs index f63fa06a..bf570123 100644 --- a/examples/url_handler/src/main.rs +++ b/examples/url_handler/src/main.rs @@ -1,6 +1,5 @@ -use iced::event::{Event, MacOS, PlatformSpecific}; +use iced::event::{self, Event}; use iced::executor; -use iced::subscription; use iced::widget::{container, text}; use iced::{ Application, Command, Element, Length, Settings, Subscription, Theme, @@ -37,9 +36,11 @@ impl Application for App { fn update(&mut self, message: Message) -> Command { match message { Message::EventOccurred(event) => { - if let Event::PlatformSpecific(PlatformSpecific::MacOS( - MacOS::ReceivedUrl(url), - )) = event + if let Event::PlatformSpecific( + event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl( + url, + )), + ) = event { self.url = Some(url); } @@ -50,7 +51,7 @@ impl Application for App { } fn subscription(&self) -> Subscription { - subscription::events().map(Message::EventOccurred) + event::listen().map(Message::EventOccurred) } fn view(&self) -> Element { diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs index 8b684514..42dfc24c 100644 --- a/examples/visible_bounds/src/main.rs +++ b/examples/visible_bounds/src/main.rs @@ -1,14 +1,14 @@ +use iced::event::{self, Event}; use iced::executor; use iced::mouse; -use iced::subscription::{self, Subscription}; use iced::theme::{self, Theme}; use iced::widget::{ column, container, horizontal_space, row, scrollable, text, vertical_space, }; use iced::window; use iced::{ - Alignment, Application, Color, Command, Element, Event, Font, Length, - Point, Rectangle, Settings, + Alignment, Application, Color, Command, Element, Font, Length, Point, + Rectangle, Settings, Subscription, }; pub fn main() -> iced::Result { @@ -163,7 +163,7 @@ impl Application for Example { } fn subscription(&self) -> Subscription { - subscription::events_with(|event, _| match event { + event::listen_with(|event, _| match event { Event::Mouse(mouse::Event::CursorMoved { position }) => { Some(Message::MouseMoved(position)) } diff --git a/futures/src/event.rs b/futures/src/event.rs new file mode 100644 index 00000000..214d2d40 --- /dev/null +++ b/futures/src/event.rs @@ -0,0 +1,59 @@ +//! Listen to runtime events. +use crate::core::event::{self, Event}; +use crate::core::window; +use crate::subscription::{self, Subscription}; +use crate::MaybeSend; + +/// Returns a [`Subscription`] to all the ignored runtime events. +/// +/// This subscription will notify your application of any [`Event`] that was +/// not captured by any widget. +pub fn listen() -> Subscription { + listen_with(|event, status| match status { + event::Status::Ignored => Some(event), + event::Status::Captured => None, + }) +} + +/// Creates a [`Subscription`] that listens and filters all the runtime events +/// with the provided function, producing messages accordingly. +/// +/// This subscription will call the provided function for every [`Event`] +/// handled by the runtime. If the function: +/// +/// - Returns `None`, the [`Event`] will be discarded. +/// - Returns `Some` message, the `Message` will be produced. +pub fn listen_with( + f: fn(Event, event::Status) -> Option, +) -> Subscription +where + Message: 'static + MaybeSend, +{ + #[derive(Hash)] + struct EventsWith; + + subscription::filter_map( + (EventsWith, f), + move |event, status| match event { + Event::Window(window::Event::RedrawRequested(_)) => None, + _ => f(event, status), + }, + ) +} + +/// Creates a [`Subscription`] that produces a message for every runtime event, +/// including the redraw request events. +/// +/// **Warning:** This [`Subscription`], if unfiltered, may produce messages in +/// an infinite loop. +pub fn listen_raw( + f: fn(Event, event::Status) -> Option, +) -> Subscription +where + Message: 'static + MaybeSend, +{ + #[derive(Hash)] + struct RawEvents; + + subscription::filter_map((RawEvents, f), f) +} diff --git a/futures/src/keyboard.rs b/futures/src/keyboard.rs new file mode 100644 index 00000000..1ddca0bb --- /dev/null +++ b/futures/src/keyboard.rs @@ -0,0 +1,61 @@ +//! Listen to keyboard events. +use crate::core; +use crate::core::keyboard::{Event, KeyCode, Modifiers}; +use crate::subscription::{self, Subscription}; +use crate::MaybeSend; + +/// Listens to keyboard key presses and calls the given function +/// map them into actual messages. +/// +/// If the function returns `None`, the key press will be simply +/// ignored. +pub fn on_key_press( + f: fn(KeyCode, Modifiers) -> Option, +) -> Subscription +where + Message: MaybeSend + 'static, +{ + #[derive(Hash)] + struct OnKeyPress; + + subscription::filter_map((OnKeyPress, f), move |event, status| { + match (event, status) { + ( + core::Event::Keyboard(Event::KeyPressed { + key_code, + modifiers, + }), + core::event::Status::Ignored, + ) => f(key_code, modifiers), + _ => None, + } + }) +} + +/// Listens to keyboard key releases and calls the given function +/// map them into actual messages. +/// +/// If the function returns `None`, the key release will be simply +/// ignored. +pub fn on_key_release( + f: fn(KeyCode, Modifiers) -> Option, +) -> Subscription +where + Message: MaybeSend + 'static, +{ + #[derive(Hash)] + struct OnKeyPress; + + subscription::filter_map((OnKeyPress, f), move |event, status| { + match (event, status) { + ( + core::Event::Keyboard(Event::KeyReleased { + key_code, + modifiers, + }), + core::event::Status::Ignored, + ) => f(key_code, modifiers), + _ => None, + } + }) +} diff --git a/futures/src/lib.rs b/futures/src/lib.rs index 34d81e1e..8e75331f 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -24,7 +24,9 @@ mod maybe_send; mod runtime; pub mod backend; +pub mod event; pub mod executor; +pub mod keyboard; pub mod subscription; pub use executor::Executor; diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index 8f78ce3a..759dd223 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -4,7 +4,6 @@ mod tracker; pub use tracker::Tracker; use crate::core::event::{self, Event}; -use crate::core::window; use crate::core::Hasher; use crate::futures::{Future, Stream}; use crate::{BoxStream, MaybeSend}; @@ -215,77 +214,6 @@ where } } -/// Returns a [`Subscription`] to all the ignored runtime events. -/// -/// This subscription will notify your application of any [`Event`] that was -/// not captured by any widget. -pub fn events() -> Subscription { - events_with(|event, status| match status { - event::Status::Ignored => Some(event), - event::Status::Captured => None, - }) -} - -/// Returns a [`Subscription`] that filters all the runtime events with the -/// provided function, producing messages accordingly. -/// -/// This subscription will call the provided function for every [`Event`] -/// handled by the runtime. If the function: -/// -/// - Returns `None`, the [`Event`] will be discarded. -/// - Returns `Some` message, the `Message` will be produced. -pub fn events_with( - f: fn(Event, event::Status) -> Option, -) -> Subscription -where - Message: 'static + MaybeSend, -{ - #[derive(Hash)] - struct EventsWith; - - Subscription::from_recipe(Runner { - id: (EventsWith, f), - spawn: move |events| { - use futures::future; - use futures::stream::StreamExt; - - events.filter_map(move |(event, status)| { - future::ready(match event { - Event::Window(window::Event::RedrawRequested(_)) => None, - _ => f(event, status), - }) - }) - }, - }) -} - -/// Returns a [`Subscription`] that produces a message for every runtime event, -/// including the redraw request events. -/// -/// **Warning:** This [`Subscription`], if unfiltered, may produce messages in -/// an infinite loop. -pub fn raw_events( - f: fn(Event, event::Status) -> Option, -) -> Subscription -where - Message: 'static + MaybeSend, -{ - #[derive(Hash)] - struct RawEvents; - - Subscription::from_recipe(Runner { - id: (RawEvents, f), - spawn: move |events| { - use futures::future; - use futures::stream::StreamExt; - - events.filter_map(move |(event, status)| { - future::ready(f(event, status)) - }) - }, - }) -} - /// Returns a [`Subscription`] that will call the given function to create and /// asynchronously run the given [`Stream`]. pub fn run(builder: fn() -> S) -> Subscription @@ -338,6 +266,25 @@ where ) } +pub(crate) fn filter_map(id: I, f: F) -> Subscription +where + I: Hash + 'static, + F: Fn(Event, event::Status) -> Option + MaybeSend + 'static, + Message: 'static + MaybeSend, +{ + Subscription::from_recipe(Runner { + id, + spawn: |events| { + use futures::future; + use futures::stream::StreamExt; + + events.filter_map(move |(event, status)| { + future::ready(f(event, status)) + }) + }, + }) +} + /// Creates a [`Subscription`] that publishes the events sent from a [`Future`] /// to an [`mpsc::Sender`] with the given bounds. /// diff --git a/runtime/src/window.rs b/runtime/src/window.rs index 5219fbfd..41816967 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -10,7 +10,8 @@ use crate::command::{self, Command}; use crate::core::time::Instant; use crate::core::window::{Event, Icon, Level, Mode, UserAttention}; use crate::core::Size; -use crate::futures::subscription::{self, Subscription}; +use crate::futures::event; +use crate::futures::Subscription; /// Subscribes to the frames of the window of the running application. /// @@ -21,7 +22,7 @@ use crate::futures::subscription::{self, Subscription}; /// In any case, this [`Subscription`] is useful to smoothly draw application-driven /// animations without missing any frames. pub fn frames() -> Subscription { - subscription::raw_events(|event, _status| match event { + event::listen_raw(|event, _status| match event { iced_core::Event::Window(Event::RedrawRequested(at)) => Some(at), _ => None, }) diff --git a/src/lib.rs b/src/lib.rs index 36f48ba2..840802cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,7 +187,6 @@ pub mod advanced; pub use style::theme; pub use crate::core::alignment; -pub use crate::core::event; pub use crate::core::gradient; pub use crate::core::{ color, Alignment, Background, BorderRadius, Color, ContentFit, Degrees, @@ -223,9 +222,16 @@ pub mod font { pub use crate::runtime::font::*; } +pub mod event { + //! Handle events of a user interface. + pub use crate::core::event::{Event, MacOS, PlatformSpecific, Status}; + pub use iced_futures::event::{listen, listen_raw, listen_with}; +} + pub mod keyboard { //! Listen and react to keyboard events. pub use crate::core::keyboard::{Event, KeyCode, Modifiers}; + pub use iced_futures::keyboard::{on_key_press, on_key_release}; } pub mod mouse { @@ -238,7 +244,7 @@ pub mod mouse { pub mod subscription { //! Listen to external events in your application. pub use iced_futures::subscription::{ - channel, events, events_with, run, run_with_id, unfold, Subscription, + channel, run, run_with_id, unfold, Subscription, }; } -- cgit From d21f0698b505d699c44e9414f902dbeca9474e39 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 02:46:19 +0200 Subject: Add hotkey support for `stopwatch` example --- examples/stopwatch/src/main.rs | 16 +++++++++++++++- futures/src/keyboard.rs | 4 ++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index 842ba3d4..0b0f0607 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -1,5 +1,6 @@ use iced::alignment; use iced::executor; +use iced::keyboard; use iced::theme::{self, Theme}; use iced::time; use iced::widget::{button, column, container, row, text}; @@ -77,12 +78,25 @@ impl Application for Stopwatch { } fn subscription(&self) -> Subscription { - match self.state { + let tick = match self.state { State::Idle => Subscription::none(), State::Ticking { .. } => { time::every(Duration::from_millis(10)).map(Message::Tick) } + }; + + fn handle_hotkey( + key_code: keyboard::KeyCode, + _modifiers: keyboard::Modifiers, + ) -> Option { + match key_code { + keyboard::KeyCode::Space => Some(Message::Toggle), + keyboard::KeyCode::R => Some(Message::Reset), + _ => None, + } } + + Subscription::batch(vec![tick, keyboard::on_key_press(handle_hotkey)]) } fn view(&self) -> Element { diff --git a/futures/src/keyboard.rs b/futures/src/keyboard.rs index 1ddca0bb..af68e1f2 100644 --- a/futures/src/keyboard.rs +++ b/futures/src/keyboard.rs @@ -44,9 +44,9 @@ where Message: MaybeSend + 'static, { #[derive(Hash)] - struct OnKeyPress; + struct OnKeyRelease; - subscription::filter_map((OnKeyPress, f), move |event, status| { + subscription::filter_map((OnKeyRelease, f), move |event, status| { match (event, status) { ( core::Event::Keyboard(Event::KeyReleased { -- cgit From 8cfad86ec1bb8d6f65eeec051e59b3ade982ef36 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 03:02:06 +0200 Subject: Unify `audit` and `detect` workflows --- .github/workflows/audit.yml | 20 +++++++++++++++++--- .github/workflows/detect.yml | 16 ---------------- 2 files changed, 17 insertions(+), 19 deletions(-) delete mode 100644 .github/workflows/detect.yml diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index ba5dc190..e9f4b0c5 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -1,12 +1,26 @@ name: Audit -on: [push] +on: + push: {} + pull_request: {} + schedule: + - cron: '0 0 * * *' jobs: - dependencies: + vulnerabilities: runs-on: ubuntu-latest steps: - uses: hecrj/setup-rust-action@v1 - name: Install cargo-audit run: cargo install cargo-audit - uses: actions/checkout@master - - name: Audit dependencies + - name: Audit vulnerabilities run: cargo audit + + artifacts: + runs-on: ubuntu-latest + steps: + - uses: hecrj/setup-rust-action@v1 + - name: Install cargo-outdated + run: cargo install cargo-outdated + - uses: actions/checkout@master + - name: Find outdated dependencies + run: cargo outdated --workspace --exit-code 1 diff --git a/.github/workflows/detect.yml b/.github/workflows/detect.yml deleted file mode 100644 index c4b7413d..00000000 --- a/.github/workflows/detect.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Detect -on: - push: {} - pull_request: {} - schedule: - - cron: '0 0 * * *' -jobs: - outdated_dependencies: - runs-on: ubuntu-latest - steps: - - uses: hecrj/setup-rust-action@v1 - - name: Install cargo-outdated - run: cargo install cargo-outdated - - uses: actions/checkout@master - - name: Detect outdated dependencies - run: cargo outdated --exit-code 1 -- cgit From d315e27451d46a815d18ed6037547d178413ede7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 03:15:02 +0200 Subject: Update example dependencies --- examples/game_of_life/Cargo.toml | 2 +- examples/integration/Cargo.toml | 9 ++------- examples/pokedex/Cargo.toml | 7 +++++-- examples/pokedex/src/main.rs | 2 +- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml index f8d21c65..9b291de8 100644 --- a/examples/game_of_life/Cargo.toml +++ b/examples/game_of_life/Cargo.toml @@ -9,7 +9,7 @@ publish = false iced.workspace = true iced.features = ["debug", "canvas", "tokio"] -itertools = "0.9" +itertools = "0.11" rustc-hash.workspace = true tokio = { workspace = true, features = ["sync"] } tracing-subscriber = "0.3" diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml index 4c55daa7..032cc75e 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -13,13 +13,8 @@ iced_widget.workspace = true tracing-subscriber = "0.3" [target.'cfg(target_arch = "wasm32")'.dependencies] -console_error_panic_hook = "0.1.7" -console_log = "0.2.0" +console_error_panic_hook = "0.1" +console_log = "1.0" log.workspace = true wasm-bindgen = "0.2" web-sys = { version = "0.3", features = ["Element", "HtmlCanvasElement", "Window", "Document"] } - -# This dependency a little bit quirky, it is deep in the tree and without `js` feature it -# refuses to work with `wasm32-unknown-unknown target`. Unfortunately, we need this patch -# to make it work -getrandom = { version = "0.2", features = ["js"] } diff --git a/examples/pokedex/Cargo.toml b/examples/pokedex/Cargo.toml index de8a5c17..bf7e1e35 100644 --- a/examples/pokedex/Cargo.toml +++ b/examples/pokedex/Cargo.toml @@ -21,5 +21,8 @@ default-features = false features = ["json", "rustls-tls"] [dependencies.rand] -version = "0.7" -features = ["wasm-bindgen"] +version = "0.8" + +[dependencies.getrandom] +version = "0.2" +features = ["js"] diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index 4482814c..8b71a269 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -153,7 +153,7 @@ impl Pokemon { let id = { let mut rng = rand::rngs::OsRng; - rng.gen_range(0, Pokemon::TOTAL) + rng.gen_range(0..Pokemon::TOTAL) }; let fetch_entry = async { -- cgit From e5afaa08924cb0f34789b5a7de1720dc91978923 Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 6 Sep 2023 21:50:59 -0400 Subject: Add access to bounds/content bounds from a scrollable viewport. (#2072) * Add access to bounds/content bounds from a scrollable viewport in order to perform certain scrollable optimizations as a consumer. * Move bounds/content_bounds after relative_offset as per feedback. --- widget/src/scrollable.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index a83ed985..d0c77e6b 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1146,6 +1146,16 @@ impl Viewport { RelativeOffset { x, y } } + + /// Returns the bounds of the current [`Viewport`]. + pub fn bounds(&self) -> Rectangle { + self.bounds + } + + /// Returns the content bounds of the current [`Viewport`]. + pub fn content_bounds(&self) -> Rectangle { + self.content_bounds + } } impl State { -- cgit From 6fd2c1552735639d96d177550e98b314bd6f79a8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 05:05:43 +0200 Subject: Host GIFs and video examples in `iced.rs` RIP Gfycat --- README.md | 14 +++++++------- examples/README.md | 20 +++++++------------- examples/bezier_tool/README.md | 4 +--- examples/color_palette/README.md | 6 ++---- examples/counter/README.md | 4 +--- examples/custom_widget/README.md | 4 +--- examples/download_progress/README.md | 4 +--- examples/events/README.md | 6 ------ examples/game_of_life/README.md | 4 +--- examples/geometry/README.md | 4 +--- examples/integration/README.md | 4 +--- examples/loading_spinners/README.md | 6 ------ examples/pane_grid/README.md | 4 +--- examples/pokedex/README.md | 4 +--- examples/progress_bar/README.md | 4 +--- examples/qr_code/README.md | 4 +--- examples/sierpinski_triangle/README.md | 4 +--- examples/solar_system/README.md | 4 +--- examples/stopwatch/README.md | 4 +--- examples/styling/README.md | 4 +--- examples/todos/README.md | 4 ++-- examples/tour/README.md | 4 ++-- src/lib.rs | 6 +++--- widget/src/pane_grid.rs | 4 ++-- 24 files changed, 40 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 431f14f9..825219aa 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ A cross-platform GUI library for Rust focused on simplicity and type-safety. Inspired by [Elm]. - - + + - - + + @@ -47,9 +47,9 @@ __Iced is currently experimental software.__ [Take a look at the roadmap], [Cross-platform support]: https://raw.githubusercontent.com/iced-rs/iced/master/docs/images/todos_desktop.jpg [the Web]: https://github.com/iced-rs/iced_web -[text inputs]: https://gfycat.com/alertcalmcrow-rust-gui -[scrollables]: https://gfycat.com/perkybaggybaboon-rust-gui -[Debug overlay with performance metrics]: https://gfycat.com/incredibledarlingbee +[text inputs]: https://iced.rs/examples/text_input.mp4 +[scrollables]: https://iced.rs/examples/scrollable.mp4 +[Debug overlay with performance metrics]: https://iced.rs/examples/debug.mp4 [Modular ecosystem]: ECOSYSTEM.md [renderer-agnostic native runtime]: runtime/ [`wgpu`]: https://github.com/gfx-rs/wgpu diff --git a/examples/README.md b/examples/README.md index 111e8910..71dad13e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -10,8 +10,8 @@ A simple UI tour that can run both on native platforms and the web! It showcases The __[`main`](tour/src/main.rs)__ file contains all the code of the example! All the cross-platform GUI is defined in terms of __state__, __messages__, __update logic__ and __view logic__.

@@ -33,8 +33,8 @@ A todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input, The example code is located in the __[`main`](todos/src/main.rs)__ file. @@ -53,9 +53,7 @@ It runs a simulation in a background thread while allowing interaction with a `C The relevant code is located in the __[`main`](game_of_life/src/main.rs)__ file.
- - - +
You can run it with `cargo run`: @@ -72,9 +70,7 @@ An example showcasing custom styling with a light and dark theme. The example code is located in the __[`main`](styling/src/main.rs)__ file.
- - - +
You can run it with `cargo run`: @@ -120,9 +116,7 @@ Since [Iced was born in May 2019], it has been powering the user interfaces in
- - - +
[Iced was born in May 2019]: https://github.com/hecrj/coffee/pull/35 diff --git a/examples/bezier_tool/README.md b/examples/bezier_tool/README.md index ebbb12cc..6dc13785 100644 --- a/examples/bezier_tool/README.md +++ b/examples/bezier_tool/README.md @@ -5,9 +5,7 @@ A Paint-like tool for drawing Bézier curves using the `Canvas` widget. The __[`main`]__ file contains all the code of the example.
- - - +
You can run it with `cargo run`: diff --git a/examples/color_palette/README.md b/examples/color_palette/README.md index f90020b1..9c135937 100644 --- a/examples/color_palette/README.md +++ b/examples/color_palette/README.md @@ -3,13 +3,11 @@ A color palette generator, based on a user-defined root color.
- - - +
You can run it with `cargo run`: ``` -cargo run --package pure_color_palette +cargo run --package color_palette ``` diff --git a/examples/counter/README.md b/examples/counter/README.md index 4d9fc5b9..53243c24 100644 --- a/examples/counter/README.md +++ b/examples/counter/README.md @@ -5,9 +5,7 @@ The classic counter example explained in the [`README`](../../README.md). The __[`main`]__ file contains all the code of the example.
- - - +
You can run it with `cargo run`: diff --git a/examples/custom_widget/README.md b/examples/custom_widget/README.md index 3d6cf902..b80e898f 100644 --- a/examples/custom_widget/README.md +++ b/examples/custom_widget/README.md @@ -5,9 +5,7 @@ A demonstration of how to build a custom widget that draws a circle. The __[`main`]__ file contains all the code of the example.
- - - +
You can run it with `cargo run`: diff --git a/examples/download_progress/README.md b/examples/download_progress/README.md index 7999ce94..19cb2966 100644 --- a/examples/download_progress/README.md +++ b/examples/download_progress/README.md @@ -5,9 +5,7 @@ A basic application that asynchronously downloads multiple dummy files of 100 MB The example implements a custom `Subscription` in the __[`download`](src/download.rs)__ module. This subscription downloads and produces messages that can be used to keep track of its progress.
- - - +
You can run it with `cargo run`: diff --git a/examples/events/README.md b/examples/events/README.md index 3c9a1cab..fd7f9b47 100644 --- a/examples/events/README.md +++ b/examples/events/README.md @@ -4,12 +4,6 @@ A log of native events displayed using a conditional `Subscription`. The __[`main`]__ file contains all the code of the example. - - You can run it with `cargo run`: ``` cargo run --package events diff --git a/examples/game_of_life/README.md b/examples/game_of_life/README.md index aa39201c..60033c1a 100644 --- a/examples/game_of_life/README.md +++ b/examples/game_of_life/README.md @@ -7,9 +7,7 @@ It runs a simulation in a background thread while allowing interaction with a `C The __[`main`]__ file contains the relevant code of the example.
- - - +
You can run it with `cargo run`: diff --git a/examples/geometry/README.md b/examples/geometry/README.md index 4d5c81cb..16be8d19 100644 --- a/examples/geometry/README.md +++ b/examples/geometry/README.md @@ -5,9 +5,7 @@ A custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [ The __[`main`]__ file contains all the code of the example.
- - - +
You can run it with `cargo run`: diff --git a/examples/integration/README.md b/examples/integration/README.md index ece9ba1e..996cdc17 100644 --- a/examples/integration/README.md +++ b/examples/integration/README.md @@ -5,9 +5,7 @@ A demonstration of how to integrate Iced in an existing [`wgpu`] application. The __[`main`]__ file contains all the code of the example.
- - - +
You can run it with `cargo run`: diff --git a/examples/loading_spinners/README.md b/examples/loading_spinners/README.md index 3573c6f6..75b88804 100644 --- a/examples/loading_spinners/README.md +++ b/examples/loading_spinners/README.md @@ -2,12 +2,6 @@ Example implementation of animated indeterminate loading spinners. - - You can run it with `cargo run`: ``` cargo run --package loading_spinners diff --git a/examples/pane_grid/README.md b/examples/pane_grid/README.md index a4cfcb7d..65357b23 100644 --- a/examples/pane_grid/README.md +++ b/examples/pane_grid/README.md @@ -15,9 +15,7 @@ This example showcases the `PaneGrid` widget, which features: The __[`main`]__ file contains all the code of the example.
- - - +
You can run it with `cargo run`: diff --git a/examples/pokedex/README.md b/examples/pokedex/README.md index 50720f57..8e8562ac 100644 --- a/examples/pokedex/README.md +++ b/examples/pokedex/README.md @@ -4,9 +4,7 @@ An application that loads a random Pokédex entry using the [PokéAPI]. All the example code can be found in the __[`main`](src/main.rs)__ file.
- - - +
You can run it on native platforms with `cargo run`: diff --git a/examples/progress_bar/README.md b/examples/progress_bar/README.md index 1e927b3c..1268ac6b 100644 --- a/examples/progress_bar/README.md +++ b/examples/progress_bar/README.md @@ -5,9 +5,7 @@ A simple progress bar that can be filled by using a slider. The __[`main`]__ file contains all the code of the example.
- - - +
You can run it with `cargo run`: diff --git a/examples/qr_code/README.md b/examples/qr_code/README.md index 2dd89c26..0d1abaa7 100644 --- a/examples/qr_code/README.md +++ b/examples/qr_code/README.md @@ -5,9 +5,7 @@ A basic QR code generator that showcases the `QRCode` widget. The __[`main`]__ file contains all the code of the example.
- - - +
You can run it with `cargo run`: diff --git a/examples/sierpinski_triangle/README.md b/examples/sierpinski_triangle/README.md index 9fd18257..8b7676d1 100644 --- a/examples/sierpinski_triangle/README.md +++ b/examples/sierpinski_triangle/README.md @@ -5,9 +5,7 @@ A simple [Sierpiński triangle](https://en.wikipedia.org/wiki/Sierpi%C5%84ski_tr Left-click add fixed point, right-click remove fixed point.
- - - +
You can run with cargo: diff --git a/examples/solar_system/README.md b/examples/solar_system/README.md index acfbc466..81ffd3a5 100644 --- a/examples/solar_system/README.md +++ b/examples/solar_system/README.md @@ -5,9 +5,7 @@ An animated solar system drawn using the `Canvas` widget and showcasing how to c The __[`main`]__ file contains all the code of the example.
- - - +
You can run it with `cargo run`: diff --git a/examples/stopwatch/README.md b/examples/stopwatch/README.md index 4cf4582e..1cf370bd 100644 --- a/examples/stopwatch/README.md +++ b/examples/stopwatch/README.md @@ -5,9 +5,7 @@ A watch with start/stop and reset buttons showcasing how to listen to time. The __[`main`]__ file contains all the code of the example.
- - - +
You can run it with `cargo run`: diff --git a/examples/styling/README.md b/examples/styling/README.md index 6c198a54..fd12300d 100644 --- a/examples/styling/README.md +++ b/examples/styling/README.md @@ -4,9 +4,7 @@ An example showcasing custom styling with a light and dark theme. All the example code is located in the __[`main`](src/main.rs)__ file.
- - - +
You can run it with `cargo run`: diff --git a/examples/todos/README.md b/examples/todos/README.md index 9c2598b9..852dd88d 100644 --- a/examples/todos/README.md +++ b/examples/todos/README.md @@ -5,8 +5,8 @@ A todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input, All the example code is located in the __[`main`]__ file. diff --git a/examples/tour/README.md b/examples/tour/README.md index 731e7e66..1c01236b 100644 --- a/examples/tour/README.md +++ b/examples/tour/README.md @@ -5,8 +5,8 @@ A simple UI tour that can run both on native platforms and the web! It showcases The __[`main`]__ file contains all the code of the example! All the cross-platform GUI is defined in terms of __state__, __messages__, __update logic__ and __view logic__. diff --git a/src/lib.rs b/src/lib.rs index 840802cc..91c78423 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,9 +20,9 @@ //! Check out the [repository] and the [examples] for more details! //! //! [Cross-platform support]: https://github.com/iced-rs/iced/blob/master/docs/images/todos_desktop.jpg?raw=true -//! [text inputs]: https://gfycat.com/alertcalmcrow-rust-gui -//! [scrollables]: https://gfycat.com/perkybaggybaboon-rust-gui -//! [Debug overlay with performance metrics]: https://gfycat.com/incredibledarlingbee +//! [text inputs]: https://iced.rs/examples/text_input.mp4 +//! [scrollables]: https://iced.rs/examples/scrollable.mp4 +//! [Debug overlay with performance metrics]: https://iced.rs/examples/debug.mp4 //! [Modular ecosystem]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md //! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/tree/0.10/runtime //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index d8c98858..40833622 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -1,6 +1,6 @@ //! Let your users split regions of your application and organize layout dynamically. //! -//! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) +//! ![Pane grid - Iced](https://iced.rs/examples/pane_grid.gif) //! //! # Example //! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, @@ -49,7 +49,7 @@ use crate::core::{ /// A collection of panes distributed using either vertical or horizontal splits /// to completely fill the space available. /// -/// [![Pane grid - Iced](https://thumbs.gfycat.com/FrailFreshAiredaleterrier-small.gif)](https://gfycat.com/frailfreshairedaleterrier) +/// ![Pane grid - Iced](https://iced.rs/examples/pane_grid.gif) /// /// This distribution of space is common in tiling window managers (like /// [`awesome`](https://awesomewm.org/), [`i3`](https://i3wm.org/), or even -- cgit From 87800095e27353557adb39ef42ee6f82a0075bc1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 05:43:03 +0200 Subject: Remove unnecessary `interpolate(flat)` in `quad.wgsl` --- wgpu/src/shader/quad.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index 87055339..023b5a6d 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -152,7 +152,7 @@ struct GradientVertexInput { @location(3) @interpolate(flat) colors_3: vec4, @location(4) @interpolate(flat) colors_4: vec4, @location(5) @interpolate(flat) offsets: vec4, - @location(6) @interpolate(flat) direction: vec4, + @location(6) direction: vec4, @location(7) position_and_scale: vec4, @location(8) border_color: vec4, @location(9) border_radius: vec4, -- cgit From 1f263051b6c2d2f2a02633d8a6277c772ae8e7f9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 05:45:51 +0200 Subject: Implement `scale` in terms of `scale_nonuniform` --- tiny_skia/src/geometry.rs | 2 +- wgpu/src/geometry.rs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index a416020f..ced5b408 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -155,7 +155,7 @@ impl Frame { } pub fn scale(&mut self, scale: f32) { - self.transform = self.transform.pre_scale(scale, scale); + self.scale_nonuniform(Vector { x: scale, y: scale }); } pub fn scale_nonuniform(&mut self, scale: Vector) { diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 2cd07a27..64b98469 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -447,9 +447,7 @@ impl Frame { /// Applies a uniform scaling to the current transform of the [`Frame`]. #[inline] pub fn scale(&mut self, scale: f32) { - self.transforms.current.raw = - self.transforms.current.raw.pre_scale(scale, scale); - self.transforms.current.is_identity = false; + self.scale_nonuniform(Vector { x: scale, y: scale }); } /// Applies a non-uniform scaling to the current transform of the [`Frame`]. -- cgit From 09965b686ea6bf82e6c13ed5331bbeb059848e4f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 05:51:39 +0200 Subject: Make `scale` methods in `Frame` generic over `f32` and `Vector` --- examples/game_of_life/src/main.rs | 8 ++++---- renderer/src/geometry.rs | 4 ++-- tiny_skia/src/geometry.rs | 8 ++++++-- wgpu/src/geometry.rs | 8 ++++++-- widget/src/qr_code.rs | 2 +- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index e951d734..1f266c8e 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -550,7 +550,7 @@ mod grid { frame.translate(center); frame.scale(self.scaling); frame.translate(self.translation); - frame.scale(Cell::SIZE as f32); + frame.scale(Cell::SIZE); let region = self.visible_region(frame.size()); @@ -576,7 +576,7 @@ mod grid { frame.translate(center); frame.scale(self.scaling); frame.translate(self.translation); - frame.scale(Cell::SIZE as f32); + frame.scale(Cell::SIZE); frame.fill_rectangle( Point::new(cell.j as f32, cell.i as f32), @@ -630,7 +630,7 @@ mod grid { frame.translate(center); frame.scale(self.scaling); frame.translate(self.translation); - frame.scale(Cell::SIZE as f32); + frame.scale(Cell::SIZE); let region = self.visible_region(frame.size()); let rows = region.rows(); @@ -834,7 +834,7 @@ mod grid { } impl Cell { - const SIZE: usize = 20; + const SIZE: u16 = 20; fn at(position: Point) -> Cell { let i = (position.y / Cell::SIZE as f32).ceil() as isize; diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs index 0e524169..1a974b7f 100644 --- a/renderer/src/geometry.rs +++ b/renderer/src/geometry.rs @@ -170,13 +170,13 @@ impl Frame { /// Applies a uniform scaling to the current transform of the [`Frame`]. #[inline] - pub fn scale(&mut self, scale: f32) { + pub fn scale(&mut self, scale: impl Into) { delegate!(self, frame, frame.scale(scale)); } /// Applies a non-uniform scaling to the current transform of the [`Frame`]. #[inline] - pub fn scale_nonuniform(&mut self, scale: Vector) { + pub fn scale_nonuniform(&mut self, scale: impl Into) { delegate!(self, frame, frame.scale_nonuniform(scale)); } diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index ced5b408..1df5aa18 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -154,11 +154,15 @@ impl Frame { .pre_concat(tiny_skia::Transform::from_rotate(angle.to_degrees())); } - pub fn scale(&mut self, scale: f32) { + pub fn scale(&mut self, scale: impl Into) { + let scale = scale.into(); + self.scale_nonuniform(Vector { x: scale, y: scale }); } - pub fn scale_nonuniform(&mut self, scale: Vector) { + pub fn scale_nonuniform(&mut self, scale: impl Into) { + let scale = scale.into(); + self.transform = self.transform.pre_scale(scale.x, scale.y); } diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 64b98469..c3e16f8c 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -446,13 +446,17 @@ impl Frame { /// Applies a uniform scaling to the current transform of the [`Frame`]. #[inline] - pub fn scale(&mut self, scale: f32) { + pub fn scale(&mut self, scale: impl Into) { + let scale = scale.into(); + self.scale_nonuniform(Vector { x: scale, y: scale }); } /// Applies a non-uniform scaling to the current transform of the [`Frame`]. #[inline] - pub fn scale_nonuniform(&mut self, scale: Vector) { + pub fn scale_nonuniform(&mut self, scale: impl Into) { + let scale = scale.into(); + self.transforms.current.raw = self.transforms.current.raw.pre_scale(scale.x, scale.y); self.transforms.current.is_identity = false; diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index 51a541fd..75409091 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -86,7 +86,7 @@ impl<'a, Message, Theme> Widget> for QRCode<'a> { let geometry = self.state.cache.draw(renderer, bounds.size(), |frame| { // Scale units to cell size - frame.scale(f32::from(self.cell_size)); + frame.scale(self.cell_size); // Draw background frame.fill_rectangle( -- cgit From 181708a1c0f4920f7491df4516b0de3f61993391 Mon Sep 17 00:00:00 2001 From: Matthias Vogelgesang Date: Mon, 28 Aug 2023 22:56:47 +0200 Subject: Compute gradients in Oklab color space --- wgpu/src/shader/quad.wgsl | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index 023b5a6d..cba7e5a4 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -230,6 +230,18 @@ fn gradient( let unit = normalize(v1); let coord_offset = dot(unit, v2) / length(v1); + let to_lms: mat3x4 = mat3x4( + vec4(0.4121656120, 0.2118591070, 0.0883097947, 0.0), + vec4(0.5362752080, 0.6807189584, 0.2818474174, 0.0), + vec4(0.0514575653, 0.1074065790, 0.6302613616, 0.0), + ); + + let to_rgb: mat3x4 = mat3x4( + vec4( 4.0767245293, -3.3072168827, 0.2307590544, 0.0), + vec4(-1.2681437731, 2.6093323231, -0.3411344290, 0.0), + vec4(-0.0041119885, -0.7034763098, 1.7068625689, 0.0), + ); + //need to store these as a var to use dynamic indexing in a loop //this is already added to wgsl spec but not in wgpu yet var colors_arr = colors; @@ -248,11 +260,15 @@ fn gradient( } if (curr_offset <= coord_offset && coord_offset <= next_offset) { - color = mix(colors_arr[i], colors_arr[i+1], smoothstep( - curr_offset, - next_offset, - coord_offset, - )); + // blend in OKLab + let factor = smoothstep(curr_offset, next_offset, coord_offset); + let lms_a = pow(colors_arr[i] * to_lms, vec3(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)); + let lms_b = pow(colors_arr[i+1] * to_lms, vec3(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)); + let mixed = mix(lms_a, lms_b, factor); + + // back to sRGB + color = to_rgb * (mixed * mixed * mixed); + color.a = mix(colors_arr[i].a, colors_arr[i+1].a, factor); } if (coord_offset >= offsets_arr[last_index]) { -- cgit From 2b746c7b253573c9194844a02afba709839f5910 Mon Sep 17 00:00:00 2001 From: Matthias Vogelgesang Date: Sat, 2 Sep 2023 13:44:45 +0200 Subject: Add gradient picker example --- examples/gradients/Cargo.toml | 8 +++ examples/gradients/src/main.rs | 136 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 examples/gradients/Cargo.toml create mode 100644 examples/gradients/src/main.rs diff --git a/examples/gradients/Cargo.toml b/examples/gradients/Cargo.toml new file mode 100644 index 00000000..1bc65a9c --- /dev/null +++ b/examples/gradients/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "gradients" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../.." } diff --git a/examples/gradients/src/main.rs b/examples/gradients/src/main.rs new file mode 100644 index 00000000..d8b7945e --- /dev/null +++ b/examples/gradients/src/main.rs @@ -0,0 +1,136 @@ +use iced::widget::{column, container, row, slider, text}; +use iced::{ + gradient, Alignment, Background, BorderRadius, Color, Element, Length, + Radians, Sandbox, Settings, +}; + +pub fn main() -> iced::Result { + Gradient::run(Settings::default()) +} + +struct Gradient { + first: Color, + second: Color, + angle: f32, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + FirstChanged(Color), + SecondChanged(Color), + AngleChanged(f32), +} + +impl Sandbox for Gradient { + type Message = Message; + + fn new() -> Self { + let first = Color::new(0.2784314, 0.0627451, 0.4117647, 1.0); + let second = Color::new(0.1882353, 0.772549, 0.8235294, 1.0); + + Self { + first, + second, + angle: 0.0, + } + } + + fn title(&self) -> String { + String::from("Color gradient") + } + + fn update(&mut self, message: Message) { + match message { + Message::FirstChanged(color) => self.first = color, + Message::SecondChanged(color) => self.second = color, + Message::AngleChanged(angle) => self.angle = angle, + } + } + + fn view(&self) -> Element { + let first = self.first; + let second = self.second; + let angle = self.angle; + + let gradient_box = container(text("")) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .style(move |_: &_| { + let gradient = gradient::Linear::new(Radians(angle)) + .add_stop(0.0, first) + .add_stop(1.0, second) + .into(); + + container::Appearance { + text_color: None, + background: Some(Background::Gradient(gradient)), + border_radius: BorderRadius::default(), + border_width: 0.0, + border_color: Color::new(0.0, 0.0, 0.0, 0.0), + } + }); + + let range = 0.0..=1.0; + let l = self.first; + let r = self.second; + + let first_color_picker = row![ + text("First").width(64), + slider(range.clone(), l.r, move |v| { + Message::FirstChanged(Color::new(v, l.g, l.b, l.a)) + }) + .step(0.01), + slider(range.clone(), l.g, move |v| { + Message::FirstChanged(Color::new(l.r, v, l.b, l.a)) + }) + .step(0.01), + slider(range.clone(), l.b, move |v| { + Message::FirstChanged(Color::new(l.r, l.g, v, l.a)) + }) + .step(0.01), + ] + .spacing(8) + .padding(8) + .align_items(Alignment::Center); + + let second_color_picker = row![ + text("Second").width(64), + slider(range.clone(), r.r, move |v| { + Message::SecondChanged(Color::new(v, r.g, r.b, r.a)) + }) + .step(0.01), + slider(range.clone(), r.g, move |v| { + Message::SecondChanged(Color::new(r.r, v, r.b, r.a)) + }) + .step(0.01), + slider(range.clone(), r.b, move |v| { + Message::SecondChanged(Color::new(r.r, r.g, v, r.a)) + }) + .step(0.01), + ] + .spacing(8) + .padding(8) + .align_items(Alignment::Center); + + let angle_picker = row![ + text("Angle").width(64), + slider(0.0..=std::f32::consts::PI * 2.0, self.angle, move |v| { + Message::AngleChanged(v) + }) + .step(0.01) + ] + .spacing(8) + .padding(8) + .align_items(Alignment::Center); + + column![ + first_color_picker, + second_color_picker, + angle_picker, + gradient_box + ] + .into() + } +} -- cgit From 10d0b257f929296b1991a440f62c87487c0076dc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 07:24:32 +0200 Subject: Use Oklab color interpolation only with `color::GAMMA_CORRECTION` --- wgpu/src/quad/gradient.rs | 19 +- wgpu/src/quad/solid.rs | 6 +- wgpu/src/shader/color/linear_rgb.wgsl | 3 + wgpu/src/shader/color/oklab.wgsl | 26 +++ wgpu/src/shader/quad.wgsl | 322 --------------------------------- wgpu/src/shader/quad/gradient.wgsl | 205 +++++++++++++++++++++ wgpu/src/shader/quad/solid.wgsl | 99 ++++++++++ wgpu/src/shader/triangle.wgsl | 160 ---------------- wgpu/src/shader/triangle/gradient.wgsl | 134 ++++++++++++++ wgpu/src/shader/triangle/solid.wgsl | 24 +++ wgpu/src/triangle.rs | 35 +++- 11 files changed, 544 insertions(+), 489 deletions(-) create mode 100644 wgpu/src/shader/color/linear_rgb.wgsl create mode 100644 wgpu/src/shader/color/oklab.wgsl create mode 100644 wgpu/src/shader/quad/gradient.wgsl create mode 100644 wgpu/src/shader/quad/solid.wgsl create mode 100644 wgpu/src/shader/triangle/gradient.wgsl create mode 100644 wgpu/src/shader/triangle/solid.wgsl diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs index 6db37252..a8e83d01 100644 --- a/wgpu/src/quad/gradient.rs +++ b/wgpu/src/quad/gradient.rs @@ -1,3 +1,4 @@ +use crate::graphics::color; use crate::graphics::gradient; use crate::quad::{self, Quad}; use crate::Buffer; @@ -78,7 +79,23 @@ impl Pipeline { device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("iced_wgpu.quad.gradient.shader"), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( - include_str!("../shader/quad.wgsl"), + if color::GAMMA_CORRECTION { + concat!( + include_str!("../shader/quad.wgsl"), + "\n", + include_str!("../shader/quad/gradient.wgsl"), + "\n", + include_str!("../shader/color/oklab.wgsl") + ) + } else { + concat!( + include_str!("../shader/quad.wgsl"), + "\n", + include_str!("../shader/quad/gradient.wgsl"), + "\n", + include_str!("../shader/color/linear_rgb.wgsl") + ) + }, )), }); diff --git a/wgpu/src/quad/solid.rs b/wgpu/src/quad/solid.rs index f8f1e3a5..9bc6b466 100644 --- a/wgpu/src/quad/solid.rs +++ b/wgpu/src/quad/solid.rs @@ -72,7 +72,11 @@ impl Pipeline { device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("iced_wgpu.quad.solid.shader"), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( - include_str!("../shader/quad.wgsl"), + concat!( + include_str!("../shader/quad.wgsl"), + "\n", + include_str!("../shader/quad/solid.wgsl"), + ), )), }); diff --git a/wgpu/src/shader/color/linear_rgb.wgsl b/wgpu/src/shader/color/linear_rgb.wgsl new file mode 100644 index 00000000..a5cf45d4 --- /dev/null +++ b/wgpu/src/shader/color/linear_rgb.wgsl @@ -0,0 +1,3 @@ +fn interpolate_color(from_: vec4, to_: vec4, factor: f32) -> vec4 { + return mix(from_, to_, factor); +} diff --git a/wgpu/src/shader/color/oklab.wgsl b/wgpu/src/shader/color/oklab.wgsl new file mode 100644 index 00000000..0dc37ba6 --- /dev/null +++ b/wgpu/src/shader/color/oklab.wgsl @@ -0,0 +1,26 @@ +const to_lms = mat3x4( + vec4(0.4121656120, 0.2118591070, 0.0883097947, 0.0), + vec4(0.5362752080, 0.6807189584, 0.2818474174, 0.0), + vec4(0.0514575653, 0.1074065790, 0.6302613616, 0.0), +); + +const to_rgb = mat3x4( + vec4( 4.0767245293, -3.3072168827, 0.2307590544, 0.0), + vec4(-1.2681437731, 2.6093323231, -0.3411344290, 0.0), + vec4(-0.0041119885, -0.7034763098, 1.7068625689, 0.0), +); + +fn interpolate_color(from_: vec4, to_: vec4, factor: f32) -> vec4 { + // To Oklab + let lms_a = pow(from_ * to_lms, vec3(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)); + let lms_b = pow(to_ * to_lms, vec3(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)); + let mixed = mix(lms_a, lms_b, factor); + + // Back to linear RGB + var color = to_rgb * (mixed * mixed * mixed); + + // Alpha interpolation + color.a = mix(from_.a, to_.a, factor); + + return color; +} diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index cba7e5a4..f919cfe2 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -37,325 +37,3 @@ fn select_border_radius(radi: vec4, position: vec2, center: vec2) rx = select(rx, ry, position.y > center.y); return rx; } - -fn unpack_u32(color: vec2) -> vec4 { - let rg: vec2 = unpack2x16float(color.x); - let ba: vec2 = unpack2x16float(color.y); - - return vec4(rg.y, rg.x, ba.y, ba.x); -} - -struct SolidVertexInput { - @location(0) v_pos: vec2, - @location(1) color: vec4, - @location(2) pos: vec2, - @location(3) scale: vec2, - @location(4) border_color: vec4, - @location(5) border_radius: vec4, - @location(6) border_width: f32, -} - -struct SolidVertexOutput { - @builtin(position) position: vec4, - @location(0) color: vec4, - @location(1) border_color: vec4, - @location(2) pos: vec2, - @location(3) scale: vec2, - @location(4) border_radius: vec4, - @location(5) border_width: f32, -} - -@vertex -fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { - var out: SolidVertexOutput; - - var pos: vec2 = input.pos * globals.scale; - var scale: vec2 = input.scale * globals.scale; - - var min_border_radius = min(input.scale.x, input.scale.y) * 0.5; - var border_radius: vec4 = vec4( - min(input.border_radius.x, min_border_radius), - min(input.border_radius.y, min_border_radius), - min(input.border_radius.z, min_border_radius), - min(input.border_radius.w, min_border_radius) - ); - - var transform: mat4x4 = mat4x4( - vec4(scale.x + 1.0, 0.0, 0.0, 0.0), - vec4(0.0, scale.y + 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(pos - vec2(0.5, 0.5), 0.0, 1.0) - ); - - out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); - out.color = input.color; - out.border_color = input.border_color; - out.pos = pos; - out.scale = scale; - out.border_radius = border_radius * globals.scale; - out.border_width = input.border_width * globals.scale; - - return out; -} - -@fragment -fn solid_fs_main( - input: SolidVertexOutput -) -> @location(0) vec4 { - var mixed_color: vec4 = input.color; - - var border_radius = select_border_radius( - input.border_radius, - input.position.xy, - (input.pos + input.scale * 0.5).xy - ); - - if (input.border_width > 0.0) { - var internal_border: f32 = max(border_radius - input.border_width, 0.0); - - var internal_distance: f32 = distance_alg( - input.position.xy, - input.pos + vec2(input.border_width, input.border_width), - input.scale - vec2(input.border_width * 2.0, input.border_width * 2.0), - internal_border - ); - - var border_mix: f32 = smoothstep( - max(internal_border - 0.5, 0.0), - internal_border + 0.5, - internal_distance - ); - - mixed_color = mix(input.color, input.border_color, vec4(border_mix, border_mix, border_mix, border_mix)); - } - - var dist: f32 = distance_alg( - vec2(input.position.x, input.position.y), - input.pos, - input.scale, - border_radius - ); - - var radius_alpha: f32 = 1.0 - smoothstep( - max(border_radius - 0.5, 0.0), - border_radius + 0.5, - dist - ); - - return vec4(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); -} - -struct GradientVertexInput { - @location(0) v_pos: vec2, - @location(1) @interpolate(flat) colors_1: vec4, - @location(2) @interpolate(flat) colors_2: vec4, - @location(3) @interpolate(flat) colors_3: vec4, - @location(4) @interpolate(flat) colors_4: vec4, - @location(5) @interpolate(flat) offsets: vec4, - @location(6) direction: vec4, - @location(7) position_and_scale: vec4, - @location(8) border_color: vec4, - @location(9) border_radius: vec4, - @location(10) border_width: f32, -} - -struct GradientVertexOutput { - @builtin(position) position: vec4, - @location(1) @interpolate(flat) colors_1: vec4, - @location(2) @interpolate(flat) colors_2: vec4, - @location(3) @interpolate(flat) colors_3: vec4, - @location(4) @interpolate(flat) colors_4: vec4, - @location(5) @interpolate(flat) offsets: vec4, - @location(6) direction: vec4, - @location(7) position_and_scale: vec4, - @location(8) border_color: vec4, - @location(9) border_radius: vec4, - @location(10) border_width: f32, -} - -@vertex -fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { - var out: GradientVertexOutput; - - var pos: vec2 = input.position_and_scale.xy * globals.scale; - var scale: vec2 = input.position_and_scale.zw * globals.scale; - - var min_border_radius = min(input.position_and_scale.z, input.position_and_scale.w) * 0.5; - var border_radius: vec4 = vec4( - min(input.border_radius.x, min_border_radius), - min(input.border_radius.y, min_border_radius), - min(input.border_radius.z, min_border_radius), - min(input.border_radius.w, min_border_radius) - ); - - var transform: mat4x4 = mat4x4( - vec4(scale.x + 1.0, 0.0, 0.0, 0.0), - vec4(0.0, scale.y + 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(pos - vec2(0.5, 0.5), 0.0, 1.0) - ); - - out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); - out.colors_1 = input.colors_1; - out.colors_2 = input.colors_2; - out.colors_3 = input.colors_3; - out.colors_4 = input.colors_4; - out.offsets = input.offsets; - out.direction = input.direction * globals.scale; - out.position_and_scale = vec4(pos, scale); - out.border_color = input.border_color; - out.border_radius = border_radius * globals.scale; - out.border_width = input.border_width * globals.scale; - - return out; -} - -fn random(coords: vec2) -> f32 { - return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453); -} - -/// Returns the current interpolated color with a max 8-stop gradient -fn gradient( - raw_position: vec2, - direction: vec4, - colors: array, 8>, - offsets: array, - last_index: i32 -) -> vec4 { - let start = direction.xy; - let end = direction.zw; - - let v1 = end - start; - let v2 = raw_position - start; - let unit = normalize(v1); - let coord_offset = dot(unit, v2) / length(v1); - - let to_lms: mat3x4 = mat3x4( - vec4(0.4121656120, 0.2118591070, 0.0883097947, 0.0), - vec4(0.5362752080, 0.6807189584, 0.2818474174, 0.0), - vec4(0.0514575653, 0.1074065790, 0.6302613616, 0.0), - ); - - let to_rgb: mat3x4 = mat3x4( - vec4( 4.0767245293, -3.3072168827, 0.2307590544, 0.0), - vec4(-1.2681437731, 2.6093323231, -0.3411344290, 0.0), - vec4(-0.0041119885, -0.7034763098, 1.7068625689, 0.0), - ); - - //need to store these as a var to use dynamic indexing in a loop - //this is already added to wgsl spec but not in wgpu yet - var colors_arr = colors; - var offsets_arr = offsets; - - var color: vec4; - - let noise_granularity: f32 = 0.3/255.0; - - for (var i: i32 = 0; i < last_index; i++) { - let curr_offset = offsets_arr[i]; - let next_offset = offsets_arr[i+1]; - - if (coord_offset <= offsets_arr[0]) { - color = colors_arr[0]; - } - - if (curr_offset <= coord_offset && coord_offset <= next_offset) { - // blend in OKLab - let factor = smoothstep(curr_offset, next_offset, coord_offset); - let lms_a = pow(colors_arr[i] * to_lms, vec3(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)); - let lms_b = pow(colors_arr[i+1] * to_lms, vec3(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0)); - let mixed = mix(lms_a, lms_b, factor); - - // back to sRGB - color = to_rgb * (mixed * mixed * mixed); - color.a = mix(colors_arr[i].a, colors_arr[i+1].a, factor); - } - - if (coord_offset >= offsets_arr[last_index]) { - color = colors_arr[last_index]; - } - } - - return color + mix(-noise_granularity, noise_granularity, random(raw_position)); -} - -@fragment -fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { - let colors = array, 8>( - unpack_u32(input.colors_1.xy), - unpack_u32(input.colors_1.zw), - unpack_u32(input.colors_2.xy), - unpack_u32(input.colors_2.zw), - unpack_u32(input.colors_3.xy), - unpack_u32(input.colors_3.zw), - unpack_u32(input.colors_4.xy), - unpack_u32(input.colors_4.zw), - ); - - let offsets_1: vec4 = unpack_u32(input.offsets.xy); - let offsets_2: vec4 = unpack_u32(input.offsets.zw); - - var offsets = array( - offsets_1.x, - offsets_1.y, - offsets_1.z, - offsets_1.w, - offsets_2.x, - offsets_2.y, - offsets_2.z, - offsets_2.w, - ); - - //TODO could just pass this in to the shader but is probably more performant to just check it here - var last_index = 7; - for (var i: i32 = 0; i <= 7; i++) { - if (offsets[i] > 1.0) { - last_index = i - 1; - break; - } - } - - var mixed_color: vec4 = gradient(input.position.xy, input.direction, colors, offsets, last_index); - - let pos = input.position_and_scale.xy; - let scale = input.position_and_scale.zw; - - var border_radius = select_border_radius( - input.border_radius, - input.position.xy, - (pos + scale * 0.5).xy - ); - - if (input.border_width > 0.0) { - var internal_border: f32 = max(border_radius - input.border_width, 0.0); - - var internal_distance: f32 = distance_alg( - input.position.xy, - pos + vec2(input.border_width, input.border_width), - scale - vec2(input.border_width * 2.0, input.border_width * 2.0), - internal_border - ); - - var border_mix: f32 = smoothstep( - max(internal_border - 0.5, 0.0), - internal_border + 0.5, - internal_distance - ); - - mixed_color = mix(mixed_color, input.border_color, vec4(border_mix, border_mix, border_mix, border_mix)); - } - - var dist: f32 = distance_alg( - input.position.xy, - pos, - scale, - border_radius - ); - - var radius_alpha: f32 = 1.0 - smoothstep( - max(border_radius - 0.5, 0.0), - border_radius + 0.5, - dist); - - return vec4(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); -} diff --git a/wgpu/src/shader/quad/gradient.wgsl b/wgpu/src/shader/quad/gradient.wgsl new file mode 100644 index 00000000..0754e97f --- /dev/null +++ b/wgpu/src/shader/quad/gradient.wgsl @@ -0,0 +1,205 @@ +struct GradientVertexInput { + @location(0) v_pos: vec2, + @location(1) @interpolate(flat) colors_1: vec4, + @location(2) @interpolate(flat) colors_2: vec4, + @location(3) @interpolate(flat) colors_3: vec4, + @location(4) @interpolate(flat) colors_4: vec4, + @location(5) @interpolate(flat) offsets: vec4, + @location(6) direction: vec4, + @location(7) position_and_scale: vec4, + @location(8) border_color: vec4, + @location(9) border_radius: vec4, + @location(10) border_width: f32, +} + +struct GradientVertexOutput { + @builtin(position) position: vec4, + @location(1) @interpolate(flat) colors_1: vec4, + @location(2) @interpolate(flat) colors_2: vec4, + @location(3) @interpolate(flat) colors_3: vec4, + @location(4) @interpolate(flat) colors_4: vec4, + @location(5) @interpolate(flat) offsets: vec4, + @location(6) direction: vec4, + @location(7) position_and_scale: vec4, + @location(8) border_color: vec4, + @location(9) border_radius: vec4, + @location(10) border_width: f32, +} + +@vertex +fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { + var out: GradientVertexOutput; + + var pos: vec2 = input.position_and_scale.xy * globals.scale; + var scale: vec2 = input.position_and_scale.zw * globals.scale; + + var min_border_radius = min(input.position_and_scale.z, input.position_and_scale.w) * 0.5; + var border_radius: vec4 = vec4( + min(input.border_radius.x, min_border_radius), + min(input.border_radius.y, min_border_radius), + min(input.border_radius.z, min_border_radius), + min(input.border_radius.w, min_border_radius) + ); + + var transform: mat4x4 = mat4x4( + vec4(scale.x + 1.0, 0.0, 0.0, 0.0), + vec4(0.0, scale.y + 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(pos - vec2(0.5, 0.5), 0.0, 1.0) + ); + + out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); + out.colors_1 = input.colors_1; + out.colors_2 = input.colors_2; + out.colors_3 = input.colors_3; + out.colors_4 = input.colors_4; + out.offsets = input.offsets; + out.direction = input.direction * globals.scale; + out.position_and_scale = vec4(pos, scale); + out.border_color = input.border_color; + out.border_radius = border_radius * globals.scale; + out.border_width = input.border_width * globals.scale; + + return out; +} + +fn random(coords: vec2) -> f32 { + return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453); +} + +/// Returns the current interpolated color with a max 8-stop gradient +fn gradient( + raw_position: vec2, + direction: vec4, + colors: array, 8>, + offsets: array, + last_index: i32 +) -> vec4 { + let start = direction.xy; + let end = direction.zw; + + let v1 = end - start; + let v2 = raw_position - start; + let unit = normalize(v1); + let coord_offset = dot(unit, v2) / length(v1); + + //need to store these as a var to use dynamic indexing in a loop + //this is already added to wgsl spec but not in wgpu yet + var colors_arr = colors; + var offsets_arr = offsets; + + var color: vec4; + + let noise_granularity: f32 = 0.3/255.0; + + for (var i: i32 = 0; i < last_index; i++) { + let curr_offset = offsets_arr[i]; + let next_offset = offsets_arr[i+1]; + + if (coord_offset <= offsets_arr[0]) { + color = colors_arr[0]; + } + + if (curr_offset <= coord_offset && coord_offset <= next_offset) { + let from_ = colors_arr[i]; + let to_ = colors_arr[i+1]; + let factor = smoothstep(curr_offset, next_offset, coord_offset); + + color = interpolate_color(from_, to_, factor); + } + + if (coord_offset >= offsets_arr[last_index]) { + color = colors_arr[last_index]; + } + } + + return color + mix(-noise_granularity, noise_granularity, random(raw_position)); +} + +@fragment +fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { + let colors = array, 8>( + unpack_u32(input.colors_1.xy), + unpack_u32(input.colors_1.zw), + unpack_u32(input.colors_2.xy), + unpack_u32(input.colors_2.zw), + unpack_u32(input.colors_3.xy), + unpack_u32(input.colors_3.zw), + unpack_u32(input.colors_4.xy), + unpack_u32(input.colors_4.zw), + ); + + let offsets_1: vec4 = unpack_u32(input.offsets.xy); + let offsets_2: vec4 = unpack_u32(input.offsets.zw); + + var offsets = array( + offsets_1.x, + offsets_1.y, + offsets_1.z, + offsets_1.w, + offsets_2.x, + offsets_2.y, + offsets_2.z, + offsets_2.w, + ); + + //TODO could just pass this in to the shader but is probably more performant to just check it here + var last_index = 7; + for (var i: i32 = 0; i <= 7; i++) { + if (offsets[i] > 1.0) { + last_index = i - 1; + break; + } + } + + var mixed_color: vec4 = gradient(input.position.xy, input.direction, colors, offsets, last_index); + + let pos = input.position_and_scale.xy; + let scale = input.position_and_scale.zw; + + var border_radius = select_border_radius( + input.border_radius, + input.position.xy, + (pos + scale * 0.5).xy + ); + + if (input.border_width > 0.0) { + var internal_border: f32 = max(border_radius - input.border_width, 0.0); + + var internal_distance: f32 = distance_alg( + input.position.xy, + pos + vec2(input.border_width, input.border_width), + scale - vec2(input.border_width * 2.0, input.border_width * 2.0), + internal_border + ); + + var border_mix: f32 = smoothstep( + max(internal_border - 0.5, 0.0), + internal_border + 0.5, + internal_distance + ); + + mixed_color = mix(mixed_color, input.border_color, vec4(border_mix, border_mix, border_mix, border_mix)); + } + + var dist: f32 = distance_alg( + input.position.xy, + pos, + scale, + border_radius + ); + + var radius_alpha: f32 = 1.0 - smoothstep( + max(border_radius - 0.5, 0.0), + border_radius + 0.5, + dist); + + return vec4(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); +} + +fn unpack_u32(color: vec2) -> vec4 { + let rg: vec2 = unpack2x16float(color.x); + let ba: vec2 = unpack2x16float(color.y); + + return vec4(rg.y, rg.x, ba.y, ba.x); +} diff --git a/wgpu/src/shader/quad/solid.wgsl b/wgpu/src/shader/quad/solid.wgsl new file mode 100644 index 00000000..ebd6d877 --- /dev/null +++ b/wgpu/src/shader/quad/solid.wgsl @@ -0,0 +1,99 @@ +struct SolidVertexInput { + @location(0) v_pos: vec2, + @location(1) color: vec4, + @location(2) pos: vec2, + @location(3) scale: vec2, + @location(4) border_color: vec4, + @location(5) border_radius: vec4, + @location(6) border_width: f32, +} + +struct SolidVertexOutput { + @builtin(position) position: vec4, + @location(0) color: vec4, + @location(1) border_color: vec4, + @location(2) pos: vec2, + @location(3) scale: vec2, + @location(4) border_radius: vec4, + @location(5) border_width: f32, +} + +@vertex +fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { + var out: SolidVertexOutput; + + var pos: vec2 = input.pos * globals.scale; + var scale: vec2 = input.scale * globals.scale; + + var min_border_radius = min(input.scale.x, input.scale.y) * 0.5; + var border_radius: vec4 = vec4( + min(input.border_radius.x, min_border_radius), + min(input.border_radius.y, min_border_radius), + min(input.border_radius.z, min_border_radius), + min(input.border_radius.w, min_border_radius) + ); + + var transform: mat4x4 = mat4x4( + vec4(scale.x + 1.0, 0.0, 0.0, 0.0), + vec4(0.0, scale.y + 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(pos - vec2(0.5, 0.5), 0.0, 1.0) + ); + + out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); + out.color = input.color; + out.border_color = input.border_color; + out.pos = pos; + out.scale = scale; + out.border_radius = border_radius * globals.scale; + out.border_width = input.border_width * globals.scale; + + return out; +} + +@fragment +fn solid_fs_main( + input: SolidVertexOutput +) -> @location(0) vec4 { + var mixed_color: vec4 = input.color; + + var border_radius = select_border_radius( + input.border_radius, + input.position.xy, + (input.pos + input.scale * 0.5).xy + ); + + if (input.border_width > 0.0) { + var internal_border: f32 = max(border_radius - input.border_width, 0.0); + + var internal_distance: f32 = distance_alg( + input.position.xy, + input.pos + vec2(input.border_width, input.border_width), + input.scale - vec2(input.border_width * 2.0, input.border_width * 2.0), + internal_border + ); + + var border_mix: f32 = smoothstep( + max(internal_border - 0.5, 0.0), + internal_border + 0.5, + internal_distance + ); + + mixed_color = mix(input.color, input.border_color, vec4(border_mix, border_mix, border_mix, border_mix)); + } + + var dist: f32 = distance_alg( + vec2(input.position.x, input.position.y), + input.pos, + input.scale, + border_radius + ); + + var radius_alpha: f32 = 1.0 - smoothstep( + max(border_radius - 0.5, 0.0), + border_radius + 0.5, + dist + ); + + return vec4(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); +} diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl index 3a2b9845..e4c19344 100644 --- a/wgpu/src/shader/triangle.wgsl +++ b/wgpu/src/shader/triangle.wgsl @@ -3,163 +3,3 @@ struct Globals { } @group(0) @binding(0) var globals: Globals; - -fn unpack_u32(color: vec2) -> vec4 { - let rg: vec2 = unpack2x16float(color.x); - let ba: vec2 = unpack2x16float(color.y); - - return vec4(rg.y, rg.x, ba.y, ba.x); -} - -struct SolidVertexInput { - @location(0) position: vec2, - @location(1) color: vec4, -} - -struct SolidVertexOutput { - @builtin(position) position: vec4, - @location(0) color: vec4, -} - -@vertex -fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { - var out: SolidVertexOutput; - - out.color = input.color; - out.position = globals.transform * vec4(input.position, 0.0, 1.0); - - return out; -} - -@fragment -fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4 { - return input.color; -} - -struct GradientVertexInput { - @location(0) v_pos: vec2, - @location(1) @interpolate(flat) colors_1: vec4, - @location(2) @interpolate(flat) colors_2: vec4, - @location(3) @interpolate(flat) colors_3: vec4, - @location(4) @interpolate(flat) colors_4: vec4, - @location(5) @interpolate(flat) offsets: vec4, - @location(6) direction: vec4, -} - -struct GradientVertexOutput { - @builtin(position) position: vec4, - @location(0) raw_position: vec2, - @location(1) @interpolate(flat) colors_1: vec4, - @location(2) @interpolate(flat) colors_2: vec4, - @location(3) @interpolate(flat) colors_3: vec4, - @location(4) @interpolate(flat) colors_4: vec4, - @location(5) @interpolate(flat) offsets: vec4, - @location(6) direction: vec4, -} - -@vertex -fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { - var output: GradientVertexOutput; - - output.position = globals.transform * vec4(input.v_pos, 0.0, 1.0); - output.raw_position = input.v_pos; - output.colors_1 = input.colors_1; - output.colors_2 = input.colors_2; - output.colors_3 = input.colors_3; - output.colors_4 = input.colors_4; - output.offsets = input.offsets; - output.direction = input.direction; - - return output; -} - -fn random(coords: vec2) -> f32 { - return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453); -} - -/// Returns the current interpolated color with a max 8-stop gradient -fn gradient( - raw_position: vec2, - direction: vec4, - colors: array, 8>, - offsets: array, - last_index: i32 -) -> vec4 { - let start = direction.xy; - let end = direction.zw; - - let v1 = end - start; - let v2 = raw_position - start; - let unit = normalize(v1); - let coord_offset = dot(unit, v2) / length(v1); - - //need to store these as a var to use dynamic indexing in a loop - //this is already added to wgsl spec but not in wgpu yet - var colors_arr = colors; - var offsets_arr = offsets; - - var color: vec4; - - let noise_granularity: f32 = 0.3/255.0; - - for (var i: i32 = 0; i < last_index; i++) { - let curr_offset = offsets_arr[i]; - let next_offset = offsets_arr[i+1]; - - if (coord_offset <= offsets_arr[0]) { - color = colors_arr[0]; - } - - if (curr_offset <= coord_offset && coord_offset <= next_offset) { - color = mix(colors_arr[i], colors_arr[i+1], smoothstep( - curr_offset, - next_offset, - coord_offset, - )); - } - - if (coord_offset >= offsets_arr[last_index]) { - color = colors_arr[last_index]; - } - } - - return color + mix(-noise_granularity, noise_granularity, random(raw_position)); -} - -@fragment -fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { - let colors = array, 8>( - unpack_u32(input.colors_1.xy), - unpack_u32(input.colors_1.zw), - unpack_u32(input.colors_2.xy), - unpack_u32(input.colors_2.zw), - unpack_u32(input.colors_3.xy), - unpack_u32(input.colors_3.zw), - unpack_u32(input.colors_4.xy), - unpack_u32(input.colors_4.zw), - ); - - let offsets_1: vec4 = unpack_u32(input.offsets.xy); - let offsets_2: vec4 = unpack_u32(input.offsets.zw); - - var offsets = array( - offsets_1.x, - offsets_1.y, - offsets_1.z, - offsets_1.w, - offsets_2.x, - offsets_2.y, - offsets_2.z, - offsets_2.w, - ); - - var last_index = 7; - for (var i: i32 = 0; i <= 7; i++) { - if (offsets[i] >= 1.0) { - last_index = i; - break; - } - } - - return gradient(input.raw_position, input.direction, colors, offsets, last_index); -} diff --git a/wgpu/src/shader/triangle/gradient.wgsl b/wgpu/src/shader/triangle/gradient.wgsl new file mode 100644 index 00000000..1a8ae3b5 --- /dev/null +++ b/wgpu/src/shader/triangle/gradient.wgsl @@ -0,0 +1,134 @@ +struct GradientVertexInput { + @location(0) v_pos: vec2, + @location(1) @interpolate(flat) colors_1: vec4, + @location(2) @interpolate(flat) colors_2: vec4, + @location(3) @interpolate(flat) colors_3: vec4, + @location(4) @interpolate(flat) colors_4: vec4, + @location(5) @interpolate(flat) offsets: vec4, + @location(6) direction: vec4, +} + +struct GradientVertexOutput { + @builtin(position) position: vec4, + @location(0) raw_position: vec2, + @location(1) @interpolate(flat) colors_1: vec4, + @location(2) @interpolate(flat) colors_2: vec4, + @location(3) @interpolate(flat) colors_3: vec4, + @location(4) @interpolate(flat) colors_4: vec4, + @location(5) @interpolate(flat) offsets: vec4, + @location(6) direction: vec4, +} + +@vertex +fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { + var output: GradientVertexOutput; + + output.position = globals.transform * vec4(input.v_pos, 0.0, 1.0); + output.raw_position = input.v_pos; + output.colors_1 = input.colors_1; + output.colors_2 = input.colors_2; + output.colors_3 = input.colors_3; + output.colors_4 = input.colors_4; + output.offsets = input.offsets; + output.direction = input.direction; + + return output; +} + +/// Returns the current interpolated color with a max 8-stop gradient +fn gradient( + raw_position: vec2, + direction: vec4, + colors: array, 8>, + offsets: array, + last_index: i32 +) -> vec4 { + let start = direction.xy; + let end = direction.zw; + + let v1 = end - start; + let v2 = raw_position - start; + let unit = normalize(v1); + let coord_offset = dot(unit, v2) / length(v1); + + //need to store these as a var to use dynamic indexing in a loop + //this is already added to wgsl spec but not in wgpu yet + var colors_arr = colors; + var offsets_arr = offsets; + + var color: vec4; + + let noise_granularity: f32 = 0.3/255.0; + + for (var i: i32 = 0; i < last_index; i++) { + let curr_offset = offsets_arr[i]; + let next_offset = offsets_arr[i+1]; + + if (coord_offset <= offsets_arr[0]) { + color = colors_arr[0]; + } + + if (curr_offset <= coord_offset && coord_offset <= next_offset) { + let from_ = colors_arr[i]; + let to_ = colors_arr[i+1]; + let factor = smoothstep(curr_offset, next_offset, coord_offset); + + color = interpolate_color(from_, to_, factor); + } + + if (coord_offset >= offsets_arr[last_index]) { + color = colors_arr[last_index]; + } + } + + return color + mix(-noise_granularity, noise_granularity, random(raw_position)); +} + +@fragment +fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { + let colors = array, 8>( + unpack_u32(input.colors_1.xy), + unpack_u32(input.colors_1.zw), + unpack_u32(input.colors_2.xy), + unpack_u32(input.colors_2.zw), + unpack_u32(input.colors_3.xy), + unpack_u32(input.colors_3.zw), + unpack_u32(input.colors_4.xy), + unpack_u32(input.colors_4.zw), + ); + + let offsets_1: vec4 = unpack_u32(input.offsets.xy); + let offsets_2: vec4 = unpack_u32(input.offsets.zw); + + var offsets = array( + offsets_1.x, + offsets_1.y, + offsets_1.z, + offsets_1.w, + offsets_2.x, + offsets_2.y, + offsets_2.z, + offsets_2.w, + ); + + var last_index = 7; + for (var i: i32 = 0; i <= 7; i++) { + if (offsets[i] >= 1.0) { + last_index = i; + break; + } + } + + return gradient(input.raw_position, input.direction, colors, offsets, last_index); +} + +fn unpack_u32(color: vec2) -> vec4 { + let rg: vec2 = unpack2x16float(color.x); + let ba: vec2 = unpack2x16float(color.y); + + return vec4(rg.y, rg.x, ba.y, ba.x); +} + +fn random(coords: vec2) -> f32 { + return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453); +} diff --git a/wgpu/src/shader/triangle/solid.wgsl b/wgpu/src/shader/triangle/solid.wgsl new file mode 100644 index 00000000..9ef81982 --- /dev/null +++ b/wgpu/src/shader/triangle/solid.wgsl @@ -0,0 +1,24 @@ +struct SolidVertexInput { + @location(0) position: vec2, + @location(1) color: vec4, +} + +struct SolidVertexOutput { + @builtin(position) position: vec4, + @location(0) color: vec4, +} + +@vertex +fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { + var out: SolidVertexOutput; + + out.color = input.color; + out.position = globals.transform * vec4(input.position, 0.0, 1.0); + + return out; +} + +@fragment +fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4 { + return input.color; +} diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index d8b23dfe..d430e607 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -487,8 +487,10 @@ mod solid { device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("iced_wgpu.triangle.solid.shader"), source: wgpu::ShaderSource::Wgsl( - std::borrow::Cow::Borrowed(include_str!( - "shader/triangle.wgsl" + std::borrow::Cow::Borrowed(concat!( + include_str!("shader/triangle.wgsl"), + "\n", + include_str!("shader/triangle/solid.wgsl"), )), ), }); @@ -537,6 +539,7 @@ mod solid { } mod gradient { + use crate::graphics::color; use crate::graphics::mesh; use crate::graphics::Antialiasing; use crate::triangle; @@ -633,9 +636,31 @@ mod gradient { device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("iced_wgpu.triangle.gradient.shader"), source: wgpu::ShaderSource::Wgsl( - std::borrow::Cow::Borrowed(include_str!( - "shader/triangle.wgsl" - )), + std::borrow::Cow::Borrowed( + if color::GAMMA_CORRECTION { + concat!( + include_str!("shader/triangle.wgsl"), + "\n", + include_str!( + "shader/triangle/gradient.wgsl" + ), + "\n", + include_str!("shader/color/oklab.wgsl") + ) + } else { + concat!( + include_str!("shader/triangle.wgsl"), + "\n", + include_str!( + "shader/triangle/gradient.wgsl" + ), + "\n", + include_str!( + "shader/color/linear_rgb.wgsl" + ) + ) + }, + ), ), }); -- cgit From adfcd8c727b896589b77322eae01f9710a86f7cc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 07:37:57 +0200 Subject: Simplify `gradients` example --- examples/gradients/src/main.rs | 99 ++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 62 deletions(-) diff --git a/examples/gradients/src/main.rs b/examples/gradients/src/main.rs index d8b7945e..ad9bfe11 100644 --- a/examples/gradients/src/main.rs +++ b/examples/gradients/src/main.rs @@ -8,16 +8,17 @@ pub fn main() -> iced::Result { Gradient::run(Settings::default()) } +#[derive(Debug, Clone, Copy)] struct Gradient { - first: Color, - second: Color, + start: Color, + end: Color, angle: f32, } #[derive(Debug, Clone, Copy)] enum Message { - FirstChanged(Color), - SecondChanged(Color), + StartChanged(Color), + EndChanged(Color), AngleChanged(f32), } @@ -25,32 +26,30 @@ impl Sandbox for Gradient { type Message = Message; fn new() -> Self { - let first = Color::new(0.2784314, 0.0627451, 0.4117647, 1.0); - let second = Color::new(0.1882353, 0.772549, 0.8235294, 1.0); + let start = Color::new(1.0, 1.0, 1.0, 1.0); + let end = Color::new(0.0, 0.0, 1.0, 1.0); Self { - first, - second, + start, + end, angle: 0.0, } } fn title(&self) -> String { - String::from("Color gradient") + String::from("Gradient") } fn update(&mut self, message: Message) { match message { - Message::FirstChanged(color) => self.first = color, - Message::SecondChanged(color) => self.second = color, + Message::StartChanged(color) => self.start = color, + Message::EndChanged(color) => self.end = color, Message::AngleChanged(angle) => self.angle = angle, } } fn view(&self) -> Element { - let first = self.first; - let second = self.second; - let angle = self.angle; + let Self { start, end, angle } = *self; let gradient_box = container(text("")) .width(Length::Fill) @@ -58,10 +57,12 @@ impl Sandbox for Gradient { .center_x() .center_y() .style(move |_: &_| { - let gradient = gradient::Linear::new(Radians(angle)) - .add_stop(0.0, first) - .add_stop(1.0, second) - .into(); + let gradient = gradient::Linear::new(Radians( + angle + std::f32::consts::PI, + )) + .add_stop(0.0, start) + .add_stop(1.0, end) + .into(); container::Appearance { text_color: None, @@ -72,48 +73,6 @@ impl Sandbox for Gradient { } }); - let range = 0.0..=1.0; - let l = self.first; - let r = self.second; - - let first_color_picker = row![ - text("First").width(64), - slider(range.clone(), l.r, move |v| { - Message::FirstChanged(Color::new(v, l.g, l.b, l.a)) - }) - .step(0.01), - slider(range.clone(), l.g, move |v| { - Message::FirstChanged(Color::new(l.r, v, l.b, l.a)) - }) - .step(0.01), - slider(range.clone(), l.b, move |v| { - Message::FirstChanged(Color::new(l.r, l.g, v, l.a)) - }) - .step(0.01), - ] - .spacing(8) - .padding(8) - .align_items(Alignment::Center); - - let second_color_picker = row![ - text("Second").width(64), - slider(range.clone(), r.r, move |v| { - Message::SecondChanged(Color::new(v, r.g, r.b, r.a)) - }) - .step(0.01), - slider(range.clone(), r.g, move |v| { - Message::SecondChanged(Color::new(r.r, v, r.b, r.a)) - }) - .step(0.01), - slider(range.clone(), r.b, move |v| { - Message::SecondChanged(Color::new(r.r, r.g, v, r.a)) - }) - .step(0.01), - ] - .spacing(8) - .padding(8) - .align_items(Alignment::Center); - let angle_picker = row![ text("Angle").width(64), slider(0.0..=std::f32::consts::PI * 2.0, self.angle, move |v| { @@ -126,11 +85,27 @@ impl Sandbox for Gradient { .align_items(Alignment::Center); column![ - first_color_picker, - second_color_picker, + color_picker("Start", self.start).map(Message::StartChanged), + color_picker("End", self.end).map(Message::EndChanged), angle_picker, gradient_box ] .into() } } + +fn color_picker(label: &str, color: Color) -> Element<'_, Color> { + row![ + text(label).width(64), + slider(0.0..=1.0, color.r, move |r| { Color { r, ..color } }) + .step(0.01), + slider(0.0..=1.0, color.g, move |g| { Color { g, ..color } }) + .step(0.01), + slider(0.0..=1.0, color.b, move |b| { Color { b, ..color } }) + .step(0.01), + ] + .spacing(8) + .padding(8) + .align_items(Alignment::Center) + .into() +} -- cgit From 21259ee745c17d0f257c7a6c356d8a491460f9d6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 07:38:51 +0200 Subject: Rename `gradients` example to `gradient` --- examples/gradient/Cargo.toml | 8 +++ examples/gradient/src/main.rs | 111 +++++++++++++++++++++++++++++++++++++++++ examples/gradients/Cargo.toml | 8 --- examples/gradients/src/main.rs | 111 ----------------------------------------- 4 files changed, 119 insertions(+), 119 deletions(-) create mode 100644 examples/gradient/Cargo.toml create mode 100644 examples/gradient/src/main.rs delete mode 100644 examples/gradients/Cargo.toml delete mode 100644 examples/gradients/src/main.rs diff --git a/examples/gradient/Cargo.toml b/examples/gradient/Cargo.toml new file mode 100644 index 00000000..1bc65a9c --- /dev/null +++ b/examples/gradient/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "gradients" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../.." } diff --git a/examples/gradient/src/main.rs b/examples/gradient/src/main.rs new file mode 100644 index 00000000..ad9bfe11 --- /dev/null +++ b/examples/gradient/src/main.rs @@ -0,0 +1,111 @@ +use iced::widget::{column, container, row, slider, text}; +use iced::{ + gradient, Alignment, Background, BorderRadius, Color, Element, Length, + Radians, Sandbox, Settings, +}; + +pub fn main() -> iced::Result { + Gradient::run(Settings::default()) +} + +#[derive(Debug, Clone, Copy)] +struct Gradient { + start: Color, + end: Color, + angle: f32, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + StartChanged(Color), + EndChanged(Color), + AngleChanged(f32), +} + +impl Sandbox for Gradient { + type Message = Message; + + fn new() -> Self { + let start = Color::new(1.0, 1.0, 1.0, 1.0); + let end = Color::new(0.0, 0.0, 1.0, 1.0); + + Self { + start, + end, + angle: 0.0, + } + } + + fn title(&self) -> String { + String::from("Gradient") + } + + fn update(&mut self, message: Message) { + match message { + Message::StartChanged(color) => self.start = color, + Message::EndChanged(color) => self.end = color, + Message::AngleChanged(angle) => self.angle = angle, + } + } + + fn view(&self) -> Element { + let Self { start, end, angle } = *self; + + let gradient_box = container(text("")) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .style(move |_: &_| { + let gradient = gradient::Linear::new(Radians( + angle + std::f32::consts::PI, + )) + .add_stop(0.0, start) + .add_stop(1.0, end) + .into(); + + container::Appearance { + text_color: None, + background: Some(Background::Gradient(gradient)), + border_radius: BorderRadius::default(), + border_width: 0.0, + border_color: Color::new(0.0, 0.0, 0.0, 0.0), + } + }); + + let angle_picker = row![ + text("Angle").width(64), + slider(0.0..=std::f32::consts::PI * 2.0, self.angle, move |v| { + Message::AngleChanged(v) + }) + .step(0.01) + ] + .spacing(8) + .padding(8) + .align_items(Alignment::Center); + + column![ + color_picker("Start", self.start).map(Message::StartChanged), + color_picker("End", self.end).map(Message::EndChanged), + angle_picker, + gradient_box + ] + .into() + } +} + +fn color_picker(label: &str, color: Color) -> Element<'_, Color> { + row![ + text(label).width(64), + slider(0.0..=1.0, color.r, move |r| { Color { r, ..color } }) + .step(0.01), + slider(0.0..=1.0, color.g, move |g| { Color { g, ..color } }) + .step(0.01), + slider(0.0..=1.0, color.b, move |b| { Color { b, ..color } }) + .step(0.01), + ] + .spacing(8) + .padding(8) + .align_items(Alignment::Center) + .into() +} diff --git a/examples/gradients/Cargo.toml b/examples/gradients/Cargo.toml deleted file mode 100644 index 1bc65a9c..00000000 --- a/examples/gradients/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "gradients" -version = "0.1.0" -edition = "2021" -publish = false - -[dependencies] -iced = { path = "../.." } diff --git a/examples/gradients/src/main.rs b/examples/gradients/src/main.rs deleted file mode 100644 index ad9bfe11..00000000 --- a/examples/gradients/src/main.rs +++ /dev/null @@ -1,111 +0,0 @@ -use iced::widget::{column, container, row, slider, text}; -use iced::{ - gradient, Alignment, Background, BorderRadius, Color, Element, Length, - Radians, Sandbox, Settings, -}; - -pub fn main() -> iced::Result { - Gradient::run(Settings::default()) -} - -#[derive(Debug, Clone, Copy)] -struct Gradient { - start: Color, - end: Color, - angle: f32, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - StartChanged(Color), - EndChanged(Color), - AngleChanged(f32), -} - -impl Sandbox for Gradient { - type Message = Message; - - fn new() -> Self { - let start = Color::new(1.0, 1.0, 1.0, 1.0); - let end = Color::new(0.0, 0.0, 1.0, 1.0); - - Self { - start, - end, - angle: 0.0, - } - } - - fn title(&self) -> String { - String::from("Gradient") - } - - fn update(&mut self, message: Message) { - match message { - Message::StartChanged(color) => self.start = color, - Message::EndChanged(color) => self.end = color, - Message::AngleChanged(angle) => self.angle = angle, - } - } - - fn view(&self) -> Element { - let Self { start, end, angle } = *self; - - let gradient_box = container(text("")) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .style(move |_: &_| { - let gradient = gradient::Linear::new(Radians( - angle + std::f32::consts::PI, - )) - .add_stop(0.0, start) - .add_stop(1.0, end) - .into(); - - container::Appearance { - text_color: None, - background: Some(Background::Gradient(gradient)), - border_radius: BorderRadius::default(), - border_width: 0.0, - border_color: Color::new(0.0, 0.0, 0.0, 0.0), - } - }); - - let angle_picker = row![ - text("Angle").width(64), - slider(0.0..=std::f32::consts::PI * 2.0, self.angle, move |v| { - Message::AngleChanged(v) - }) - .step(0.01) - ] - .spacing(8) - .padding(8) - .align_items(Alignment::Center); - - column![ - color_picker("Start", self.start).map(Message::StartChanged), - color_picker("End", self.end).map(Message::EndChanged), - angle_picker, - gradient_box - ] - .into() - } -} - -fn color_picker(label: &str, color: Color) -> Element<'_, Color> { - row![ - text(label).width(64), - slider(0.0..=1.0, color.r, move |r| { Color { r, ..color } }) - .step(0.01), - slider(0.0..=1.0, color.g, move |g| { Color { g, ..color } }) - .step(0.01), - slider(0.0..=1.0, color.b, move |b| { Color { b, ..color } }) - .step(0.01), - ] - .spacing(8) - .padding(8) - .align_items(Alignment::Center) - .into() -} -- cgit From f83fb9b6cd221ecfc02d468df128dbb8b3751c3f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 07:40:27 +0200 Subject: Use `Color::WHITE` in `gradient` example --- examples/gradient/src/main.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/gradient/src/main.rs b/examples/gradient/src/main.rs index ad9bfe11..dd50ebc9 100644 --- a/examples/gradient/src/main.rs +++ b/examples/gradient/src/main.rs @@ -26,12 +26,9 @@ impl Sandbox for Gradient { type Message = Message; fn new() -> Self { - let start = Color::new(1.0, 1.0, 1.0, 1.0); - let end = Color::new(0.0, 0.0, 1.0, 1.0); - Self { - start, - end, + start: Color::WHITE, + end: Color::new(0.0, 0.0, 1.0, 1.0), angle: 0.0, } } -- cgit From d818deb4cdfcfeffa88e649e3e27aa477a336f4c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 07:50:59 +0200 Subject: Increase threshold of `enum-variant-names` lint --- clippy.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/clippy.toml b/clippy.toml index 0d4e02f0..02f00a06 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1,2 @@ too-many-arguments-threshold = 20 +enum-variant-name-threshold = 10 -- cgit From fc261a539b28dcd5f1898107a7c69c0719f3bbbf Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 07:53:26 +0200 Subject: Fix name of `gradient` example package --- examples/gradient/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gradient/Cargo.toml b/examples/gradient/Cargo.toml index 1bc65a9c..2dea2c4f 100644 --- a/examples/gradient/Cargo.toml +++ b/examples/gradient/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "gradients" +name = "gradient" version = "0.1.0" edition = "2021" publish = false -- cgit From 6ff2e48feb7c0c3f65a3e6d298fc1c73356dc740 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 07:53:34 +0200 Subject: Use `Default` for `container::Appearance` in `gradient` example --- examples/gradient/src/main.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/gradient/src/main.rs b/examples/gradient/src/main.rs index dd50ebc9..91a65f1e 100644 --- a/examples/gradient/src/main.rs +++ b/examples/gradient/src/main.rs @@ -1,7 +1,7 @@ +use iced::gradient; use iced::widget::{column, container, row, slider, text}; use iced::{ - gradient, Alignment, Background, BorderRadius, Color, Element, Length, - Radians, Sandbox, Settings, + Alignment, Background, Color, Element, Length, Radians, Sandbox, Settings, }; pub fn main() -> iced::Result { @@ -62,11 +62,8 @@ impl Sandbox for Gradient { .into(); container::Appearance { - text_color: None, background: Some(Background::Gradient(gradient)), - border_radius: BorderRadius::default(), - border_width: 0.0, - border_color: Color::new(0.0, 0.0, 0.0, 0.0), + ..Default::default() } }); -- cgit From d6be3ab68246b08ea3f8988833e3b266b524dd1d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 07:55:09 +0200 Subject: Use `horizontal_space` instead of empty `text` widget in `gradient` example --- examples/gradient/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/gradient/src/main.rs b/examples/gradient/src/main.rs index 91a65f1e..3ba08a06 100644 --- a/examples/gradient/src/main.rs +++ b/examples/gradient/src/main.rs @@ -1,5 +1,5 @@ use iced::gradient; -use iced::widget::{column, container, row, slider, text}; +use iced::widget::{column, container, horizontal_space, row, slider, text}; use iced::{ Alignment, Background, Color, Element, Length, Radians, Sandbox, Settings, }; @@ -48,7 +48,7 @@ impl Sandbox for Gradient { fn view(&self) -> Element { let Self { start, end, angle } = *self; - let gradient_box = container(text("")) + let gradient_box = container(horizontal_space(Length::Fill)) .width(Length::Fill) .height(Length::Fill) .center_x() -- cgit From c1139898c50d22ac3b711801b1058aacef514030 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Sep 2023 07:55:54 +0200 Subject: Remove unnecessary centering in `gradient` example --- examples/gradient/src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/gradient/src/main.rs b/examples/gradient/src/main.rs index 3ba08a06..0b32e73c 100644 --- a/examples/gradient/src/main.rs +++ b/examples/gradient/src/main.rs @@ -51,8 +51,6 @@ impl Sandbox for Gradient { let gradient_box = container(horizontal_space(Length::Fill)) .width(Length::Fill) .height(Length::Fill) - .center_x() - .center_y() .style(move |_: &_| { let gradient = gradient::Linear::new(Radians( angle + std::f32::consts::PI, -- cgit From d2294737c2e28b3b3050d7bf820bbca09896b00e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 8 Sep 2023 01:58:52 +0200 Subject: Use `Radians` as a number directly in `gradient` example --- core/Cargo.toml | 1 + core/src/angle.rs | 45 ++++++++++++++++++++++++++++++++++++++++--- examples/gradient/src/main.rs | 22 +++++++++------------ widget/src/slider.rs | 4 ++-- 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 8859e91e..7acb7511 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -15,6 +15,7 @@ bitflags.workspace = true log.workspace = true thiserror.workspace = true twox-hash.workspace = true +num-traits.workspace = true palette.workspace = true palette.optional = true diff --git a/core/src/angle.rs b/core/src/angle.rs index 75a57c76..91fc2ba7 100644 --- a/core/src/angle.rs +++ b/core/src/angle.rs @@ -1,17 +1,56 @@ use crate::{Point, Rectangle, Vector}; + use std::f32::consts::PI; +use std::ops::RangeInclusive; -#[derive(Debug, Copy, Clone, PartialEq)] /// Degrees +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] pub struct Degrees(pub f32); -#[derive(Debug, Copy, Clone, PartialEq)] /// Radians +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] pub struct Radians(pub f32); +impl Radians { + /// The range of radians of a circle. + pub const RANGE: RangeInclusive = Radians(0.0)..=Radians(2.0 * PI); +} + impl From for Radians { fn from(degrees: Degrees) -> Self { - Radians(degrees.0 * PI / 180.0) + Self(degrees.0 * PI / 180.0) + } +} + +impl From for Radians { + fn from(radians: f32) -> Self { + Self(radians) + } +} + +impl From for Radians { + fn from(radians: u8) -> Self { + Self(f32::from(radians)) + } +} + +impl From for f64 { + fn from(radians: Radians) -> Self { + Self::from(radians.0) + } +} + +impl num_traits::FromPrimitive for Radians { + fn from_i64(n: i64) -> Option { + Some(Self(n as f32)) + } + + fn from_u64(n: u64) -> Option { + Some(Self(n as f32)) + } + + fn from_f64(n: f64) -> Option { + Some(Self(n as f32)) } } diff --git a/examples/gradient/src/main.rs b/examples/gradient/src/main.rs index 0b32e73c..1bf5822d 100644 --- a/examples/gradient/src/main.rs +++ b/examples/gradient/src/main.rs @@ -12,14 +12,14 @@ pub fn main() -> iced::Result { struct Gradient { start: Color, end: Color, - angle: f32, + angle: Radians, } #[derive(Debug, Clone, Copy)] enum Message { StartChanged(Color), EndChanged(Color), - AngleChanged(f32), + AngleChanged(Radians), } impl Sandbox for Gradient { @@ -29,7 +29,7 @@ impl Sandbox for Gradient { Self { start: Color::WHITE, end: Color::new(0.0, 0.0, 1.0, 1.0), - angle: 0.0, + angle: Radians(0.0), } } @@ -52,12 +52,10 @@ impl Sandbox for Gradient { .width(Length::Fill) .height(Length::Fill) .style(move |_: &_| { - let gradient = gradient::Linear::new(Radians( - angle + std::f32::consts::PI, - )) - .add_stop(0.0, start) - .add_stop(1.0, end) - .into(); + let gradient = gradient::Linear::new(angle) + .add_stop(0.0, start) + .add_stop(1.0, end) + .into(); container::Appearance { background: Some(Background::Gradient(gradient)), @@ -67,10 +65,8 @@ impl Sandbox for Gradient { let angle_picker = row![ text("Angle").width(64), - slider(0.0..=std::f32::consts::PI * 2.0, self.angle, move |v| { - Message::AngleChanged(v) - }) - .step(0.01) + slider(Radians::RANGE, self.angle, Message::AngleChanged) + .step(0.01) ] .spacing(8) .padding(8) diff --git a/widget/src/slider.rs b/widget/src/slider.rs index e41be7c9..bd73ea79 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -137,8 +137,8 @@ where } /// Sets the step size of the [`Slider`]. - pub fn step(mut self, step: T) -> Self { - self.step = step; + pub fn step(mut self, step: impl Into) -> Self { + self.step = step.into(); self } } -- cgit From 90cbab18b95a2a90a6a527280a6ca461203ea1b3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 8 Sep 2023 02:36:17 +0200 Subject: Fine-tune `Radians::to_distance` An angle of 0 radians will "point" to the top-center of a `Rectangle` and then increase clockwise. --- core/src/angle.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/core/src/angle.rs b/core/src/angle.rs index 91fc2ba7..c8f3f013 100644 --- a/core/src/angle.rs +++ b/core/src/angle.rs @@ -1,6 +1,6 @@ use crate::{Point, Rectangle, Vector}; -use std::f32::consts::PI; +use std::f32::consts::{FRAC_PI_2, PI}; use std::ops::RangeInclusive; /// Degrees @@ -57,15 +57,16 @@ impl num_traits::FromPrimitive for Radians { impl Radians { /// Calculates the line in which the [`Angle`] intercepts the `bounds`. pub fn to_distance(&self, bounds: &Rectangle) -> (Point, Point) { - let v1 = Vector::new(f32::cos(self.0), f32::sin(self.0)); + let angle = self.0 - FRAC_PI_2; + let r = Vector::new(f32::cos(angle), f32::sin(angle)); - let distance_to_rect = f32::min( - f32::abs((bounds.y - bounds.center().y) / v1.y), - f32::abs(((bounds.x + bounds.width) - bounds.center().x) / v1.x), + let distance_to_rect = f32::max( + f32::abs(r.x * bounds.width / 2.0), + f32::abs(r.y * bounds.height / 2.0), ); - let start = bounds.center() + v1 * distance_to_rect; - let end = bounds.center() - v1 * distance_to_rect; + let start = bounds.center() - r * distance_to_rect; + let end = bounds.center() + r * distance_to_rect; (start, end) } -- cgit From 3450987355be7fe029db112474d06613929b54c7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 9 Sep 2023 11:21:32 +0200 Subject: Invalidate existing paragraphs when new fonts are loaded --- core/src/text.rs | 68 ++++++++++++++++++++++++++++++++++-------- graphics/src/renderer.rs | 23 ++++++++++++++ graphics/src/text.rs | 44 ++++++++++++++++++++------- graphics/src/text/paragraph.rs | 15 ++++++++-- tiny_skia/src/text.rs | 5 +--- wgpu/src/text.rs | 5 +--- 6 files changed, 127 insertions(+), 33 deletions(-) diff --git a/core/src/text.rs b/core/src/text.rs index c59c683a..f5e9abc4 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -172,18 +172,14 @@ pub trait Renderer: crate::Renderer { paragraph: &mut Self::Paragraph, text: Text<'_, Self::Font>, ) { - if paragraph.content() != text.content - || paragraph.text_size() != text.size - || paragraph.line_height().to_absolute(text.size) - != text.line_height.to_absolute(text.size) - || paragraph.font() != text.font - || paragraph.shaping() != text.shaping - || paragraph.horizontal_alignment() != text.horizontal_alignment - || paragraph.vertical_alignment() != text.vertical_alignment - { - *paragraph = self.create_paragraph(text); - } else if paragraph.bounds() != text.bounds { - self.resize_paragraph(paragraph, text.bounds); + match compare(paragraph, text) { + Difference::None => {} + Difference::Bounds => { + self.resize_paragraph(paragraph, text.bounds); + } + Difference::Shape => { + *paragraph = self.create_paragraph(text); + } } } @@ -255,3 +251,51 @@ pub trait Paragraph: Default { self.min_bounds().height } } + +/// The difference detected in some text. +/// +/// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some +/// [`Text`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Difference { + /// No difference. + /// + /// The text can be reused as it is! + None, + + /// A bounds difference. + /// + /// This normally means a relayout is necessary, but the shape of the text can + /// be reused. + Bounds, + + /// A shape difference. + /// + /// The contents, alignment, sizes, fonts, or any other essential attributes + /// of the shape of the text have changed. A complete reshape and relayout of + /// the text is necessary. + Shape, +} + +/// Compares a [`Paragraph`] with some desired [`Text`] and returns the +/// [`Difference`]. +pub fn compare( + paragraph: &impl Paragraph, + text: Text<'_, Font>, +) -> Difference { + if paragraph.content() != text.content + || paragraph.text_size() != text.size + || paragraph.line_height().to_absolute(text.size) + != text.line_height.to_absolute(text.size) + || paragraph.font() != text.font + || paragraph.shaping() != text.shaping + || paragraph.horizontal_alignment() != text.horizontal_alignment + || paragraph.vertical_alignment() != text.vertical_alignment + { + Difference::Shape + } else if paragraph.bounds() != text.bounds { + Difference::Bounds + } else { + Difference::None + } +} diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index f93f4a6d..d4df29a5 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -162,6 +162,29 @@ where text::Paragraph::with_text(text, self.backend.font_system()) } + fn update_paragraph( + &self, + paragraph: &mut Self::Paragraph, + text: Text<'_, Self::Font>, + ) { + let font_system = self.backend.font_system(); + + if paragraph.version() != font_system.version() { + // The font system has changed, paragraph fonts may be outdated + *paragraph = self.create_paragraph(text); + } else { + match core::text::compare(paragraph, text) { + core::text::Difference::None => {} + core::text::Difference::Bounds => { + self.resize_paragraph(paragraph, text.bounds); + } + core::text::Difference::Shape => { + *paragraph = self.create_paragraph(text); + } + } + } + } + fn resize_paragraph( &self, paragraph: &mut Self::Paragraph, diff --git a/graphics/src/text.rs b/graphics/src/text.rs index bbe9d7cb..bc06aa3c 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -10,30 +10,54 @@ use crate::core::font::{self, Font}; use crate::core::text::Shaping; use crate::core::Size; +use std::borrow::Cow; use std::sync::{self, Arc, RwLock}; #[allow(missing_debug_implementations)] -pub struct FontSystem(RwLock); +pub struct FontSystem { + raw: RwLock, + version: Version, +} impl FontSystem { pub fn new() -> Self { - FontSystem(RwLock::new(cosmic_text::FontSystem::new_with_fonts( - [cosmic_text::fontdb::Source::Binary(Arc::new( - include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), - ))] - .into_iter(), - ))) + FontSystem { + raw: RwLock::new(cosmic_text::FontSystem::new_with_fonts( + [cosmic_text::fontdb::Source::Binary(Arc::new( + include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), + ))] + .into_iter(), + )), + version: Version::default(), + } } pub fn get_mut(&mut self) -> &mut cosmic_text::FontSystem { - self.0.get_mut().expect("Lock font system") + self.raw.get_mut().expect("Lock font system") + } + + pub fn write( + &self, + ) -> (sync::RwLockWriteGuard<'_, cosmic_text::FontSystem>, Version) { + (self.raw.write().expect("Write font system"), self.version) } - pub fn write(&self) -> sync::RwLockWriteGuard<'_, cosmic_text::FontSystem> { - self.0.write().expect("Write font system") + pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { + let _ = self.get_mut().db_mut().load_font_source( + cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())), + ); + + self.version = Version(self.version.0 + 1); + } + + pub fn version(&self) -> Version { + self.version } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct Version(u32); + impl Default for FontSystem { fn default() -> Self { Self::new() diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index ee7c04c8..cd12bc8f 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -19,6 +19,7 @@ struct Internal { vertical_alignment: alignment::Vertical, bounds: Size, min_bounds: Size, + version: text::Version, } impl Paragraph { @@ -27,9 +28,9 @@ impl Paragraph { } pub fn with_text(text: Text<'_, Font>, font_system: &FontSystem) -> Self { - log::trace!("\nAllocating paragraph: {}", text.content); + log::trace!("Allocating paragraph: {}", text.content); - let mut font_system = font_system.write(); + let (mut font_system, version) = font_system.write(); let mut buffer = cosmic_text::Buffer::new( &mut font_system, @@ -63,6 +64,7 @@ impl Paragraph { shaping: text.shaping, bounds: text.bounds, min_bounds, + version, }))) } @@ -70,6 +72,10 @@ impl Paragraph { &self.internal().buffer } + pub fn version(&self) -> text::Version { + self.internal().version + } + pub fn downgrade(&self) -> Weak { let paragraph = self.internal(); @@ -89,8 +95,10 @@ impl Paragraph { match Arc::try_unwrap(paragraph) { Ok(mut internal) => { + let (mut font_system, _) = font_system.write(); + internal.buffer.set_size( - &mut font_system.write(), + &mut font_system, new_bounds.width, new_bounds.height, ); @@ -246,6 +254,7 @@ impl Default for Internal { vertical_alignment: alignment::Vertical::Top, bounds: Size::ZERO, min_bounds: Size::ZERO, + version: text::Version::default(), } } } diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 2bf35e76..820e7bd8 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -9,7 +9,6 @@ use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; use std::cell::RefCell; use std::collections::hash_map; -use std::sync::Arc; #[allow(missing_debug_implementations)] pub struct Pipeline { @@ -32,9 +31,7 @@ impl Pipeline { } pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - self.font_system.get_mut().db_mut().load_font_source( - cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())), - ); + self.font_system.load_font(bytes); self.cache = RefCell::new(Cache::new()); } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index a1ec511b..bd4f3e06 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -7,7 +7,6 @@ use crate::layer::Text; use std::borrow::Cow; use std::cell::RefCell; -use std::sync::Arc; #[allow(missing_debug_implementations)] pub struct Pipeline { @@ -47,9 +46,7 @@ impl Pipeline { } pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - let _ = self.font_system.get_mut().db_mut().load_font_source( - glyphon::fontdb::Source::Binary(Arc::new(bytes.into_owned())), - ); + self.font_system.load_font(bytes); self.cache = RefCell::new(Cache::new()); } -- cgit From 3cc605b70f543313cb665465ac169d0c85c446ab Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 9 Sep 2023 12:36:00 +0200 Subject: Implement `Icon` support for `TextInput` --- widget/src/text_input.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index f36b5616..aa35b5e4 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -511,7 +511,20 @@ where ); if let Some(icon) = icon { - let icon_width = 0.0; // TODO + let icon_text = Text { + line_height, + content: &icon.code_point.to_string(), + font: icon.font, + size: icon.size.unwrap_or_else(|| renderer.default_size()), + bounds: Size::new(f32::INFINITY, text_bounds.height), + horizontal_alignment: alignment::Horizontal::Center, + vertical_alignment: alignment::Vertical::Center, + shaping: text::Shaping::Advanced, + }; + + renderer.update_paragraph(&mut state.icon, icon_text); + + let icon_width = state.icon.min_width(); let mut text_node = layout::Node::new( text_bounds - Size::new(icon_width + icon.spacing, 0.0), @@ -1053,10 +1066,14 @@ pub fn draw( appearance.background, ); - if let Some(_icon) = icon { - let _icon_layout = children_layout.next().unwrap(); + if icon.is_some() { + let icon_layout = children_layout.next().unwrap(); - // TODO + renderer.fill_paragraph( + &state.icon, + icon_layout.bounds().center(), + appearance.icon_color, + ); } let text = value.to_string(); @@ -1206,6 +1223,7 @@ pub fn mouse_interaction( pub struct State { value: P, placeholder: P, + icon: P, is_focused: Option, is_dragging: bool, is_pasting: Option, @@ -1233,6 +1251,7 @@ impl State

{ Self { value: P::default(), placeholder: P::default(), + icon: P::default(), is_focused: None, is_dragging: false, is_pasting: None, -- cgit From 9c2ad457d80e8254955396ad034df0dd5f2b59b3 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 9 Sep 2023 13:59:18 +0200 Subject: Add webgl feature for web examples --- examples/counter/Cargo.toml | 4 ++++ examples/integration/Cargo.toml | 3 +++ examples/todos/Cargo.toml | 3 +++ examples/tour/Cargo.toml | 4 ++++ 4 files changed, 14 insertions(+) diff --git a/examples/counter/Cargo.toml b/examples/counter/Cargo.toml index 1e9bba6b..22f86064 100644 --- a/examples/counter/Cargo.toml +++ b/examples/counter/Cargo.toml @@ -7,3 +7,7 @@ publish = false [dependencies] iced.workspace = true + +[target.'cfg(target_arch = "wasm32")'.dependencies] +iced.workspace = true +iced.features = ["webgl"] diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml index 032cc75e..bf70a619 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -13,6 +13,9 @@ iced_widget.workspace = true tracing-subscriber = "0.3" [target.'cfg(target_arch = "wasm32")'.dependencies] +iced_wgpu.workspace = true +iced_wgpu.features = ["webgl"] + console_error_panic_hook = "0.1" console_log = "1.0" log.workspace = true diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index fea20375..3334d84f 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -18,6 +18,9 @@ async-std.workspace = true directories-next = "2.0" [target.'cfg(target_arch = "wasm32")'.dependencies] +iced.workspace = true +iced.features = ["debug", "webgl"] + web-sys = { workspace = true, features = ["Window", "Storage"] } wasm-timer.workspace = true diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index 21206ecb..9de017ce 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -10,3 +10,7 @@ iced.workspace = true iced.features = ["image", "debug"] tracing-subscriber = "0.3" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +iced.workspace = true +iced.features = ["image", "debug", "webgl"] -- cgit From e562544807b196dafac9195ea8137958698755e5 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 9 Sep 2023 14:09:48 +0200 Subject: Add logging for web in tour example --- examples/tour/Cargo.toml | 5 +++++ examples/tour/src/main.rs | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index 9de017ce..11920e0d 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -9,8 +9,13 @@ publish = false iced.workspace = true iced.features = ["image", "debug"] +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] tracing-subscriber = "0.3" [target.'cfg(target_arch = "wasm32")'.dependencies] iced.workspace = true iced.features = ["image", "debug", "webgl"] + +console_error_panic_hook = "0.1" +console_log = "1.0" +log.workspace = true diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index af508206..f0e3c0cd 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -8,6 +8,13 @@ use iced::widget::{Button, Column, Container, Slider}; use iced::{Color, Element, Font, Length, Renderer, Sandbox, Settings}; pub fn main() -> iced::Result { + #[cfg(target_arch = "wasm32")] + { + console_log::init_with_level(log::Level::Debug).expect("Initialize logger"); + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + } + + #[cfg(not(target_arch = "wasm32"))] tracing_subscriber::fmt::init(); Tour::run(Settings::default()) -- cgit From 52da89ac531cdf93b0e74afdd5bf6ad366551f82 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 9 Sep 2023 14:11:40 +0200 Subject: Add instructions to run web examples --- examples/counter/README.md | 8 ++++++++ examples/todos/README.md | 9 ++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/examples/counter/README.md b/examples/counter/README.md index 53243c24..18761bba 100644 --- a/examples/counter/README.md +++ b/examples/counter/README.md @@ -13,4 +13,12 @@ You can run it with `cargo run`: cargo run --package counter ``` +The web version can be run with [`trunk`]: + +``` +cd examples/counter +trunk serve +``` + [`main`]: src/main.rs +[`trunk`]: https://trunkrs.dev/ diff --git a/examples/todos/README.md b/examples/todos/README.md index 852dd88d..5e42f166 100644 --- a/examples/todos/README.md +++ b/examples/todos/README.md @@ -14,7 +14,14 @@ You can run the native version with `cargo run`: ``` cargo run --package todos ``` -We have not yet implemented a `LocalStorage` version of the auto-save feature. Therefore, it does not work on web _yet_! + +The web version can be run with [`trunk`]: + +``` +cd examples/todos +trunk serve +``` [`main`]: src/main.rs [TodoMVC]: http://todomvc.com/ +[`trunk`]: https://trunkrs.dev/ -- cgit From 052fe0edaeb0d6ce096375751eec02b953faf2a1 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 9 Sep 2023 14:15:11 +0200 Subject: Dont use tracing-subscriber dependency on web --- examples/integration/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml index bf70a619..6b9fcc57 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -10,6 +10,7 @@ iced_winit.workspace = true iced_wgpu.workspace = true iced_widget.workspace = true +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] tracing-subscriber = "0.3" [target.'cfg(target_arch = "wasm32")'.dependencies] -- cgit From 89d9f1d7d2202029028a487df1dd11b0665a7517 Mon Sep 17 00:00:00 2001 From: Matthias Vogelgesang Date: Sat, 9 Sep 2023 12:24:47 +0200 Subject: Fix majority of unresolved documentation links --- core/src/angle.rs | 2 +- core/src/gradient.rs | 2 +- core/src/shell.rs | 4 +-- core/src/window/icon.rs | 2 +- futures/src/runtime.rs | 5 ++-- futures/src/subscription.rs | 6 ++-- futures/src/subscription/tracker.rs | 3 ++ graphics/src/geometry.rs | 4 +-- graphics/src/gradient.rs | 4 +-- graphics/src/mesh.rs | 2 +- renderer/src/geometry.rs | 4 +-- renderer/src/settings.rs | 4 +-- runtime/src/lib.rs | 25 +--------------- runtime/src/overlay/nested.rs | 4 ++- runtime/src/program/state.rs | 2 +- runtime/src/user_interface.rs | 4 +-- src/lib.rs | 6 ++-- src/settings.rs | 2 +- src/window/icon.rs | 2 +- wgpu/src/geometry.rs | 4 +-- widget/src/canvas/event.rs | 2 +- widget/src/canvas/program.rs | 11 +++---- widget/src/checkbox.rs | 6 ++-- widget/src/combo_box.rs | 8 ++++-- widget/src/helpers.rs | 54 ++++++++++++++++++----------------- widget/src/overlay/menu.rs | 2 +- widget/src/pane_grid/configuration.rs | 4 +-- widget/src/pane_grid/content.rs | 4 +-- widget/src/pane_grid/node.rs | 2 +- widget/src/pane_grid/pane.rs | 2 +- widget/src/pane_grid/split.rs | 2 +- widget/src/pane_grid/state.rs | 40 ++++++++++++++------------ widget/src/pane_grid/title_bar.rs | 6 ++-- widget/src/pick_list.rs | 2 +- widget/src/radio.rs | 2 +- widget/src/text_input.rs | 2 +- widget/src/text_input/value.rs | 2 +- widget/src/toggler.rs | 6 ++-- winit/src/application/state.rs | 4 +-- winit/src/conversion.rs | 2 +- 40 files changed, 117 insertions(+), 137 deletions(-) diff --git a/core/src/angle.rs b/core/src/angle.rs index c8f3f013..102b69cf 100644 --- a/core/src/angle.rs +++ b/core/src/angle.rs @@ -55,7 +55,7 @@ impl num_traits::FromPrimitive for Radians { } impl Radians { - /// Calculates the line in which the [`Angle`] intercepts the `bounds`. + /// Calculates the line in which the angle intercepts the `bounds`. pub fn to_distance(&self, bounds: &Rectangle) -> (Point, Point) { let angle = self.0 - FRAC_PI_2; let r = Vector::new(f32::cos(angle), f32::sin(angle)); diff --git a/core/src/gradient.rs b/core/src/gradient.rs index e19622fb..576c9e4d 100644 --- a/core/src/gradient.rs +++ b/core/src/gradient.rs @@ -9,7 +9,7 @@ use std::cmp::Ordering; /// /// For a gradient which can be used as a fill on a canvas, see [`iced_graphics::Gradient`]. pub enum Gradient { - /// A linear gradient interpolates colors along a direction at a specific [`Angle`]. + /// A linear gradient interpolates colors along a direction at a specific angle. Linear(Linear), } diff --git a/core/src/shell.rs b/core/src/shell.rs index 74a5c616..246c937a 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -35,7 +35,7 @@ impl<'a, Message> Shell<'a, Message> { self.messages.push(message); } - /// Requests a new frame to be drawn at the given [`Instant`]. + /// Requests a new frame to be drawn. pub fn request_redraw(&mut self, request: window::RedrawRequest) { match self.redraw_request { None => { @@ -48,7 +48,7 @@ impl<'a, Message> Shell<'a, Message> { } } - /// Returns the requested [`Instant`] a redraw should happen, if any. + /// Returns the request a redraw should happen, if any. pub fn redraw_request(&self) -> Option { self.redraw_request } diff --git a/core/src/window/icon.rs b/core/src/window/icon.rs index 31868ecf..2fc48e3b 100644 --- a/core/src/window/icon.rs +++ b/core/src/window/icon.rs @@ -49,7 +49,7 @@ impl Icon { } #[derive(Debug, thiserror::Error)] -/// An error produced when using [`Icon::from_rgba`] with invalid arguments. +/// An error produced when using [`from_rgba`] with invalid arguments. pub enum Error { /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be /// safely interpreted as 32bpp RGBA pixels. diff --git a/futures/src/runtime.rs b/futures/src/runtime.rs index 2241a494..16111b36 100644 --- a/futures/src/runtime.rs +++ b/futures/src/runtime.rs @@ -9,9 +9,9 @@ use std::marker::PhantomData; /// A batteries-included runtime of commands and subscriptions. /// /// If you have an [`Executor`], a [`Runtime`] can be leveraged to run any -/// [`Command`] or [`Subscription`] and get notified of the results! +/// `Command` or [`Subscription`] and get notified of the results! /// -/// [`Command`]: crate::Command +/// [`Subscription`]: crate::Subscription #[derive(Debug)] pub struct Runtime { executor: Executor, @@ -75,6 +75,7 @@ where /// [`Tracker::update`] to learn more about this! /// /// [`Tracker::update`]: subscription::Tracker::update + /// [`Subscription`]: crate::Subscription pub fn track( &mut self, recipes: impl IntoIterator< diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index 759dd223..d40125e3 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -19,16 +19,14 @@ pub type EventStream = BoxStream<(Event, event::Status)>; /// A request to listen to external events. /// -/// Besides performing async actions on demand with [`Command`], most +/// Besides performing async actions on demand with `Command`, most /// applications also need to listen to external events passively. /// -/// A [`Subscription`] is normally provided to some runtime, like a [`Command`], +/// A [`Subscription`] is normally provided to some runtime, like a `Command`, /// and it will generate events as long as the user keeps requesting it. /// /// For instance, you can use a [`Subscription`] to listen to a WebSocket /// connection, keyboard presses, mouse events, time ticks, etc. -/// -/// [`Command`]: crate::Command #[must_use = "`Subscription` must be returned to runtime to take effect"] pub struct Subscription { recipes: Vec>>, diff --git a/futures/src/subscription/tracker.rs b/futures/src/subscription/tracker.rs index ae71cd25..3a83da09 100644 --- a/futures/src/subscription/tracker.rs +++ b/futures/src/subscription/tracker.rs @@ -14,6 +14,8 @@ use std::hash::Hasher as _; /// If you have an application that continuously returns a [`Subscription`], /// you can use a [`Tracker`] to keep track of the different recipes and keep /// its executions alive. +/// +/// [`Subscription`]: crate::Subscription #[derive(Debug, Default)] pub struct Tracker { subscriptions: HashMap, @@ -51,6 +53,7 @@ impl Tracker { /// the [`Tracker`] changes. /// /// [`Recipe`]: crate::subscription::Recipe + /// [`Subscription`]: crate::Subscription pub fn update( &mut self, recipes: impl Iterator>>, diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index 7cd3dd3a..d7d6a0aa 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -14,11 +14,11 @@ pub use text::Text; pub use crate::gradient::{self, Gradient}; -/// A renderer capable of drawing some [`Geometry`]. +/// A renderer capable of drawing some [`Self::Geometry`]. pub trait Renderer: crate::core::Renderer { /// The kind of geometry this renderer can draw. type Geometry; - /// Draws the given layers of [`Geometry`]. + /// Draws the given layers of [`Self::Geometry`]. fn draw(&mut self, layers: Vec); } diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs index 4db565d8..f5f5f8b4 100644 --- a/graphics/src/gradient.rs +++ b/graphics/src/gradient.rs @@ -1,4 +1,4 @@ -//! A gradient that can be used as a [`Fill`] for some geometry. +//! A gradient that can be used as a fill for some geometry. //! //! For a gradient that you can use as a background variant for a widget, see [`Gradient`]. //! @@ -53,7 +53,7 @@ pub struct Linear { } impl Linear { - /// Creates a new [`Builder`]. + /// Creates a new [`Linear`] builder. pub fn new(start: Point, end: Point) -> Self { Self { start, diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index cfb5a60f..041986cf 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -41,7 +41,7 @@ impl Damage for Mesh { } } -/// A set of [`Vertex2D`] and indices representing a list of triangles. +/// A set of vertices and indices representing a list of triangles. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Indexed { /// The vertices of the mesh diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs index 1a974b7f..1ecb0a43 100644 --- a/renderer/src/geometry.rs +++ b/renderer/src/geometry.rs @@ -96,13 +96,11 @@ impl Frame { /// resulting glyphs will not be rotated or scaled properly. /// /// Additionally, all text will be rendered on top of all the layers of - /// a [`Canvas`]. Therefore, it is currently only meant to be used for + /// a `Canvas`. Therefore, it is currently only meant to be used for /// overlays, which is the most common use case. /// /// Support for vectorial text is planned, and should address all these /// limitations. - /// - /// [`Canvas`]: crate::widget::Canvas pub fn fill_text(&mut self, text: impl Into) { delegate!(self, frame, frame.fill_text(text)); } diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs index 2e51f339..318ae718 100644 --- a/renderer/src/settings.rs +++ b/renderer/src/settings.rs @@ -1,9 +1,7 @@ use crate::core::Font; use crate::graphics::Antialiasing; -/// The settings of a [`Backend`]. -/// -/// [`Backend`]: crate::Backend +/// The settings of a Backend. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Settings { /// The default [`Font`] to use. diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 59c70ff7..dc631d47 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -2,32 +2,9 @@ //! //! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/raw/improvement/update-ecosystem-and-roadmap/docs/graphs/native.png) //! -//! `iced_native` takes [`iced_core`] and builds a native runtime on top of it, -//! featuring: -//! -//! - A custom layout engine, greatly inspired by [`druid`] -//! - Event handling for all the built-in widgets -//! - A renderer-agnostic API -//! -//! To achieve this, it introduces a couple of reusable interfaces: -//! -//! - A [`Widget`] trait, which is used to implement new widgets: from layout -//! requirements to event and drawing logic. -//! - A bunch of `Renderer` traits, meant to keep the crate renderer-agnostic. -//! -//! # Usage -//! The strategy to use this crate depends on your particular use case. If you -//! want to: -//! - Implement a custom shell or integrate it in your own system, check out the -//! [`UserInterface`] type. -//! - Build a new renderer, see the [renderer] module. -//! - Build a custom widget, start at the [`Widget`] trait. +//! `iced_runtime` takes [`iced_core`] and builds a native runtime on top of it. //! //! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.10/core -//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.10/winit -//! [`druid`]: https://github.com/xi-editor/druid -//! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle -//! [renderer]: crate::renderer #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index b729f769..21b6f7c1 100644 --- a/runtime/src/overlay/nested.rs +++ b/runtime/src/overlay/nested.rs @@ -6,7 +6,7 @@ use crate::core::renderer; use crate::core::widget; use crate::core::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size}; -/// An [`Overlay`] container that displays nested overlays +/// An overlay container that displays nested overlays #[allow(missing_debug_implementations)] pub struct Nested<'a, Message, Renderer> { overlay: overlay::Element<'a, Message, Renderer>, @@ -27,6 +27,8 @@ where } /// Returns the layout [`Node`] of the [`Nested`] overlay. + /// + /// [`Node`]: layout::Node pub fn layout( &mut self, renderer: &Renderer, diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index 35df6078..9aa2d550 100644 --- a/runtime/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -175,7 +175,7 @@ where (uncaptured_events, command) } - /// Applies [`widget::Operation`]s to the [`State`] + /// Applies [`Operation`]s to the [`State`] pub fn operate( &mut self, renderer: &mut P::Renderer, diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index e529c004..165a85ae 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -356,7 +356,7 @@ where /// It returns the current [`mouse::Interaction`]. You should update the /// icon of the mouse cursor accordingly in your system. /// - /// [`Renderer`]: crate::Renderer + /// [`Renderer`]: crate::core::Renderer /// /// # Example /// We can finally draw our [counter](index.html#usage) by @@ -619,7 +619,7 @@ pub enum State { /// The [`UserInterface`] is up-to-date and can be reused without /// rebuilding. Updated { - /// The [`Instant`] when a redraw should be performed. + /// The [`window::RedrawRequest`] when a redraw should be performed. redraw_request: Option, }, } diff --git a/src/lib.rs b/src/lib.rs index 91c78423..8780d276 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -258,11 +258,11 @@ pub mod system { pub mod overlay { //! Display interactive elements on top of other widgets. - /// A generic [`Overlay`]. + /// A generic overlay. /// - /// This is an alias of an `iced_native` element with a default `Renderer`. + /// This is an alias of an [`overlay::Element`] with a default `Renderer`. /// - /// [`Overlay`]: iced_native::Overlay + /// [`overlay::Element`]: crate::core::overlay::Element pub type Element<'a, Message, Renderer = crate::Renderer> = crate::core::overlay::Element<'a, Message, Renderer>; diff --git a/src/settings.rs b/src/settings.rs index 0dd46584..8659603c 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -23,7 +23,7 @@ pub struct Settings { /// The default [`Font`] to be used. /// - /// By default, it uses [`Font::SansSerif`]. + /// By default, it uses [`Family::SansSerif`](crate::font::Family::SansSerif). pub default_font: Font, /// The text size that will be used by default. diff --git a/src/window/icon.rs b/src/window/icon.rs index 0cb206b3..ef71c228 100644 --- a/src/window/icon.rs +++ b/src/window/icon.rs @@ -10,7 +10,7 @@ use std::path::Path; /// Creates an icon from an image file. /// -/// This will return an error in case the file is missing at run-time. You may prefer [`Self::from_file_data`] instead. +/// This will return an error in case the file is missing at run-time. You may prefer [`from_file_data`] instead. #[cfg(feature = "image")] pub fn from_file>(icon_path: P) -> Result { let icon = image::io::Reader::open(icon_path)?.decode()?.to_rgba8(); diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index c3e16f8c..63a59c05 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -310,13 +310,11 @@ impl Frame { /// resulting glyphs will not be rotated or scaled properly. /// /// Additionally, all text will be rendered on top of all the layers of - /// a [`Canvas`]. Therefore, it is currently only meant to be used for + /// a `Canvas`. Therefore, it is currently only meant to be used for /// overlays, which is the most common use case. /// /// Support for vectorial text is planned, and should address all these /// limitations. - /// - /// [`Canvas`]: crate::widget::Canvas pub fn fill_text(&mut self, text: impl Into) { let text = text.into(); diff --git a/widget/src/canvas/event.rs b/widget/src/canvas/event.rs index 4508c184..1288365f 100644 --- a/widget/src/canvas/event.rs +++ b/widget/src/canvas/event.rs @@ -7,7 +7,7 @@ pub use crate::core::event::Status; /// A [`Canvas`] event. /// -/// [`Canvas`]: crate::widget::Canvas +/// [`Canvas`]: crate::Canvas #[derive(Debug, Clone, Copy, PartialEq)] pub enum Event { /// A mouse event. diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs index b3f6175e..2ac23061 100644 --- a/widget/src/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -8,7 +8,7 @@ use crate::graphics::geometry; /// A [`Program`] can mutate internal state and produce messages for an /// application. /// -/// [`Canvas`]: crate::widget::Canvas +/// [`Canvas`]: crate::Canvas pub trait Program where Renderer: geometry::Renderer, @@ -26,7 +26,7 @@ where /// /// By default, this method does and returns nothing. /// - /// [`Canvas`]: crate::widget::Canvas + /// [`Canvas`]: crate::Canvas fn update( &self, _state: &mut Self::State, @@ -42,8 +42,9 @@ where /// [`Geometry`] can be easily generated with a [`Frame`] or stored in a /// [`Cache`]. /// - /// [`Frame`]: crate::widget::canvas::Frame - /// [`Cache`]: crate::widget::canvas::Cache + /// [`Geometry`]: crate::canvas::Geometry + /// [`Frame`]: crate::canvas::Frame + /// [`Cache`]: crate::canvas::Cache fn draw( &self, state: &Self::State, @@ -58,7 +59,7 @@ where /// The interaction returned will be in effect even if the cursor position /// is out of bounds of the program's [`Canvas`]. /// - /// [`Canvas`]: crate::widget::Canvas + /// [`Canvas`]: crate::Canvas fn mouse_interaction( &self, _state: &Self::State, diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 310a67ed..e574c3cc 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -122,7 +122,7 @@ where self } - /// Sets the text [`LineHeight`] of the [`Checkbox`]. + /// Sets the text [`text::LineHeight`] of the [`Checkbox`]. pub fn text_line_height( mut self, line_height: impl Into, @@ -137,9 +137,9 @@ where self } - /// Sets the [`Font`] of the text of the [`Checkbox`]. + /// Sets the [`Renderer::Font`] of the text of the [`Checkbox`]. /// - /// [`Font`]: crate::text::Renderer::Font + /// [`Renderer::Font`]: crate::core::text::Renderer pub fn font(mut self, font: impl Into) -> Self { self.font = Some(font.into()); self diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 690ef27c..0dc12354 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -20,7 +20,7 @@ use std::time::Instant; /// /// 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`]. +/// as a Menu. #[allow(missing_debug_implementations)] pub struct ComboBox<'a, T, Message, Renderer = crate::Renderer> where @@ -131,14 +131,16 @@ where self } - /// Sets the [`Font`] of the [`ComboBox`]. + /// Sets the [`Renderer::Font`] of the [`ComboBox`]. + /// + /// [`Renderer::Font`]: text::Renderer 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`]. + /// Sets the [`text_input::Icon`] of the [`ComboBox`]. pub fn icon(mut self, icon: text_input::Icon) -> Self { self.text_input = self.text_input.icon(icon); self diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 9c3c83a9..289720eb 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -25,7 +25,7 @@ use std::ops::RangeInclusive; /// Creates a [`Column`] with the given children. /// -/// [`Column`]: widget::Column +/// [`Column`]: crate::Column #[macro_export] macro_rules! column { () => ( @@ -38,7 +38,7 @@ macro_rules! column { /// Creates a [`Row`] with the given children. /// -/// [`Row`]: widget::Row +/// [`Row`]: crate::Row #[macro_export] macro_rules! row { () => ( @@ -51,7 +51,7 @@ macro_rules! row { /// Creates a new [`Container`] with the provided content. /// -/// [`Container`]: widget::Container +/// [`Container`]: crate::Container pub fn container<'a, Message, Renderer>( content: impl Into>, ) -> Container<'a, Message, Renderer> @@ -64,7 +64,7 @@ where /// Creates a new [`Column`] with the given children. /// -/// [`Column`]: widget::Column +/// [`Column`]: crate::Column pub fn column( children: Vec>, ) -> Column<'_, Message, Renderer> { @@ -73,7 +73,7 @@ pub fn column( /// Creates a new [`Row`] with the given children. /// -/// [`Row`]: widget::Row +/// [`Row`]: crate::Row pub fn row( children: Vec>, ) -> Row<'_, Message, Renderer> { @@ -82,7 +82,7 @@ pub fn row( /// Creates a new [`Scrollable`] with the provided content. /// -/// [`Scrollable`]: widget::Scrollable +/// [`Scrollable`]: crate::Scrollable pub fn scrollable<'a, Message, Renderer>( content: impl Into>, ) -> Scrollable<'a, Message, Renderer> @@ -95,7 +95,7 @@ where /// Creates a new [`Button`] with the provided content. /// -/// [`Button`]: widget::Button +/// [`Button`]: crate::Button pub fn button<'a, Message, Renderer>( content: impl Into>, ) -> Button<'a, Message, Renderer> @@ -109,8 +109,8 @@ where /// Creates a new [`Tooltip`] with the provided content, tooltip text, and [`tooltip::Position`]. /// -/// [`Tooltip`]: widget::Tooltip -/// [`tooltip::Position`]: widget::tooltip::Position +/// [`Tooltip`]: crate::Tooltip +/// [`tooltip::Position`]: crate::tooltip::Position pub fn tooltip<'a, Message, Renderer>( content: impl Into>, tooltip: impl ToString, @@ -125,7 +125,7 @@ where /// Creates a new [`Text`] widget with the provided content. /// -/// [`Text`]: widget::Text +/// [`Text`]: core::widget::Text pub fn text<'a, Renderer>(text: impl ToString) -> Text<'a, Renderer> where Renderer: core::text::Renderer, @@ -136,7 +136,7 @@ where /// Creates a new [`Checkbox`]. /// -/// [`Checkbox`]: widget::Checkbox +/// [`Checkbox`]: crate::Checkbox pub fn checkbox<'a, Message, Renderer>( label: impl Into, is_checked: bool, @@ -151,7 +151,7 @@ where /// Creates a new [`Radio`]. /// -/// [`Radio`]: widget::Radio +/// [`Radio`]: crate::Radio pub fn radio( label: impl Into, value: V, @@ -169,7 +169,7 @@ where /// Creates a new [`Toggler`]. /// -/// [`Toggler`]: widget::Toggler +/// [`Toggler`]: crate::Toggler pub fn toggler<'a, Message, Renderer>( label: impl Into>, is_checked: bool, @@ -184,7 +184,7 @@ where /// Creates a new [`TextInput`]. /// -/// [`TextInput`]: widget::TextInput +/// [`TextInput`]: crate::TextInput pub fn text_input<'a, Message, Renderer>( placeholder: &str, value: &str, @@ -199,7 +199,7 @@ where /// Creates a new [`Slider`]. /// -/// [`Slider`]: widget::Slider +/// [`Slider`]: crate::Slider pub fn slider<'a, T, Message, Renderer>( range: std::ops::RangeInclusive, value: T, @@ -216,7 +216,7 @@ where /// Creates a new [`VerticalSlider`]. /// -/// [`VerticalSlider`]: widget::VerticalSlider +/// [`VerticalSlider`]: crate::VerticalSlider pub fn vertical_slider<'a, T, Message, Renderer>( range: std::ops::RangeInclusive, value: T, @@ -233,7 +233,7 @@ where /// Creates a new [`PickList`]. /// -/// [`PickList`]: widget::PickList +/// [`PickList`]: crate::PickList pub fn pick_list<'a, Message, Renderer, T>( options: impl Into>, selected: Option, @@ -255,7 +255,7 @@ where /// Creates a new [`ComboBox`]. /// -/// [`ComboBox`]: widget::ComboBox +/// [`ComboBox`]: crate::ComboBox pub fn combo_box<'a, T, Message, Renderer>( state: &'a combo_box::State, placeholder: &str, @@ -272,21 +272,21 @@ where /// Creates a new horizontal [`Space`] with the given [`Length`]. /// -/// [`Space`]: widget::Space +/// [`Space`]: crate::Space pub fn horizontal_space(width: impl Into) -> Space { Space::with_width(width) } /// Creates a new vertical [`Space`] with the given [`Length`]. /// -/// [`Space`]: widget::Space +/// [`Space`]: crate::Space pub fn vertical_space(height: impl Into) -> Space { Space::with_height(height) } /// Creates a horizontal [`Rule`] with the given height. /// -/// [`Rule`]: widget::Rule +/// [`Rule`]: crate::Rule pub fn horizontal_rule(height: impl Into) -> Rule where Renderer: core::Renderer, @@ -297,7 +297,7 @@ where /// Creates a vertical [`Rule`] with the given width. /// -/// [`Rule`]: widget::Rule +/// [`Rule`]: crate::Rule pub fn vertical_rule(width: impl Into) -> Rule where Renderer: core::Renderer, @@ -312,7 +312,7 @@ where /// * an inclusive range of possible values, and /// * the current value of the [`ProgressBar`]. /// -/// [`ProgressBar`]: widget::ProgressBar +/// [`ProgressBar`]: crate::ProgressBar pub fn progress_bar( range: RangeInclusive, value: f32, @@ -326,7 +326,7 @@ where /// Creates a new [`Image`]. /// -/// [`Image`]: widget::Image +/// [`Image`]: crate::Image #[cfg(feature = "image")] pub fn image(handle: impl Into) -> crate::Image { crate::Image::new(handle.into()) @@ -334,8 +334,8 @@ pub fn image(handle: impl Into) -> crate::Image { /// Creates a new [`Svg`] widget from the given [`Handle`]. /// -/// [`Svg`]: widget::Svg -/// [`Handle`]: widget::svg::Handle +/// [`Svg`]: crate::Svg +/// [`Handle`]: crate::svg::Handle #[cfg(feature = "svg")] pub fn svg( handle: impl Into, @@ -348,6 +348,8 @@ where } /// Creates a new [`Canvas`]. +/// +/// [`Canvas`]: crate::Canvas #[cfg(feature = "canvas")] pub fn canvas( program: P, diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index f7bdeef6..711a8891 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -89,7 +89,7 @@ where self } - /// Sets the text [`LineHeight`] of the [`Menu`]. + /// Sets the text [`text::LineHeight`] of the [`Menu`]. pub fn text_line_height( mut self, line_height: impl Into, diff --git a/widget/src/pane_grid/configuration.rs b/widget/src/pane_grid/configuration.rs index ddbc3bc2..b8aa2c7d 100644 --- a/widget/src/pane_grid/configuration.rs +++ b/widget/src/pane_grid/configuration.rs @@ -2,7 +2,7 @@ use crate::pane_grid::Axis; /// The arrangement of a [`PaneGrid`]. /// -/// [`PaneGrid`]: crate::widget::PaneGrid +/// [`PaneGrid`]: super::PaneGrid #[derive(Debug, Clone)] pub enum Configuration { /// A split of the available space. @@ -21,6 +21,6 @@ pub enum Configuration { }, /// A [`Pane`]. /// - /// [`Pane`]: crate::widget::pane_grid::Pane + /// [`Pane`]: super::Pane Pane(T), } diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index e890e41a..218adcd5 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -10,7 +10,7 @@ use crate::pane_grid::{Draggable, TitleBar}; /// The content of a [`Pane`]. /// -/// [`Pane`]: crate::widget::pane_grid::Pane +/// [`Pane`]: super::Pane #[allow(missing_debug_implementations)] pub struct Content<'a, Message, Renderer = crate::Renderer> where @@ -87,7 +87,7 @@ where /// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`]. /// - /// [`Renderer`]: crate::Renderer + /// [`Renderer`]: crate::core::Renderer pub fn draw( &self, tree: &Tree, diff --git a/widget/src/pane_grid/node.rs b/widget/src/pane_grid/node.rs index 6de5920f..3c707f15 100644 --- a/widget/src/pane_grid/node.rs +++ b/widget/src/pane_grid/node.rs @@ -5,7 +5,7 @@ use std::collections::BTreeMap; /// A layout node of a [`PaneGrid`]. /// -/// [`PaneGrid`]: crate::widget::PaneGrid +/// [`PaneGrid`]: super::PaneGrid #[derive(Debug, Clone)] pub enum Node { /// The region of this [`Node`] is split into two. diff --git a/widget/src/pane_grid/pane.rs b/widget/src/pane_grid/pane.rs index d6fbab83..cabf55c1 100644 --- a/widget/src/pane_grid/pane.rs +++ b/widget/src/pane_grid/pane.rs @@ -1,5 +1,5 @@ /// A rectangular region in a [`PaneGrid`] used to display widgets. /// -/// [`PaneGrid`]: crate::widget::PaneGrid +/// [`PaneGrid`]: super::PaneGrid #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Pane(pub(super) usize); diff --git a/widget/src/pane_grid/split.rs b/widget/src/pane_grid/split.rs index 8132272a..ce021978 100644 --- a/widget/src/pane_grid/split.rs +++ b/widget/src/pane_grid/split.rs @@ -1,5 +1,5 @@ /// A divider that splits a region in a [`PaneGrid`] into two different panes. /// -/// [`PaneGrid`]: crate::widget::PaneGrid +/// [`PaneGrid`]: super::PaneGrid #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Split(pub(super) usize); diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index 6fd15890..28a52cf0 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -1,6 +1,6 @@ //! The state of a [`PaneGrid`]. //! -//! [`PaneGrid`]: crate::widget::PaneGrid +//! [`PaneGrid`]: super::PaneGrid use crate::core::{Point, Size}; use crate::pane_grid::{ Axis, Configuration, Direction, Edge, Node, Pane, Region, Split, Target, @@ -18,23 +18,23 @@ use std::collections::HashMap; /// provided to the view function of [`PaneGrid::new`] for displaying each /// [`Pane`]. /// -/// [`PaneGrid`]: crate::widget::PaneGrid -/// [`PaneGrid::new`]: crate::widget::PaneGrid::new +/// [`PaneGrid`]: super::PaneGrid +/// [`PaneGrid::new`]: super::PaneGrid::new #[derive(Debug, Clone)] pub struct State { /// The panes of the [`PaneGrid`]. /// - /// [`PaneGrid`]: crate::widget::PaneGrid + /// [`PaneGrid`]: super::PaneGrid pub panes: HashMap, /// The internal state of the [`PaneGrid`]. /// - /// [`PaneGrid`]: crate::widget::PaneGrid + /// [`PaneGrid`]: super::PaneGrid pub internal: Internal, /// The maximized [`Pane`] of the [`PaneGrid`]. /// - /// [`PaneGrid`]: crate::widget::PaneGrid + /// [`PaneGrid`]: super::PaneGrid pub(super) maximized: Option, } @@ -236,6 +236,8 @@ impl State { } /// Move [`Pane`] to an [`Edge`] of the [`PaneGrid`]. + /// + /// [`PaneGrid`]: super::PaneGrid pub fn move_to_edge(&mut self, pane: &Pane, edge: Edge) { match edge { Edge::Top => { @@ -269,8 +271,8 @@ impl State { /// If you want to swap panes on drag and drop in your [`PaneGrid`], you /// will need to call this method when handling a [`DragEvent`]. /// - /// [`PaneGrid`]: crate::widget::PaneGrid - /// [`DragEvent`]: crate::widget::pane_grid::DragEvent + /// [`PaneGrid`]: super::PaneGrid + /// [`DragEvent`]: super::DragEvent pub fn swap(&mut self, a: &Pane, b: &Pane) { self.internal.layout.update(&|node| match node { Node::Split { .. } => {} @@ -292,8 +294,8 @@ impl State { /// If you want to enable resize interactions in your [`PaneGrid`], you will /// need to call this method when handling a [`ResizeEvent`]. /// - /// [`PaneGrid`]: crate::widget::PaneGrid - /// [`ResizeEvent`]: crate::widget::pane_grid::ResizeEvent + /// [`PaneGrid`]: super::PaneGrid + /// [`ResizeEvent`]: super::ResizeEvent pub fn resize(&mut self, split: &Split, ratio: f32) { let _ = self.internal.layout.resize(split, ratio); } @@ -315,7 +317,7 @@ impl State { /// Maximize the given [`Pane`]. Only this pane will be rendered by the /// [`PaneGrid`] until [`Self::restore()`] is called. /// - /// [`PaneGrid`]: crate::widget::PaneGrid + /// [`PaneGrid`]: super::PaneGrid pub fn maximize(&mut self, pane: &Pane) { self.maximized = Some(*pane); } @@ -323,14 +325,14 @@ impl State { /// Restore the currently maximized [`Pane`] to it's normal size. All panes /// will be rendered by the [`PaneGrid`]. /// - /// [`PaneGrid`]: crate::widget::PaneGrid + /// [`PaneGrid`]: super::PaneGrid pub fn restore(&mut self) { let _ = self.maximized.take(); } /// Returns the maximized [`Pane`] of the [`PaneGrid`]. /// - /// [`PaneGrid`]: crate::widget::PaneGrid + /// [`PaneGrid`]: super::PaneGrid pub fn maximized(&self) -> Option { self.maximized } @@ -338,7 +340,7 @@ impl State { /// The internal state of a [`PaneGrid`]. /// -/// [`PaneGrid`]: crate::widget::PaneGrid +/// [`PaneGrid`]: super::PaneGrid #[derive(Debug, Clone)] pub struct Internal { layout: Node, @@ -349,7 +351,7 @@ impl Internal { /// Initializes the [`Internal`] state of a [`PaneGrid`] from a /// [`Configuration`]. /// - /// [`PaneGrid`]: crate::widget::PaneGrid + /// [`PaneGrid`]: super::PaneGrid pub fn from_configuration( panes: &mut HashMap, content: Configuration, @@ -394,16 +396,16 @@ impl Internal { /// The current action of a [`PaneGrid`]. /// -/// [`PaneGrid`]: crate::widget::PaneGrid +/// [`PaneGrid`]: super::PaneGrid #[derive(Debug, Clone, Copy, PartialEq)] pub enum Action { /// The [`PaneGrid`] is idle. /// - /// [`PaneGrid`]: crate::widget::PaneGrid + /// [`PaneGrid`]: super::PaneGrid Idle, /// A [`Pane`] in the [`PaneGrid`] is being dragged. /// - /// [`PaneGrid`]: crate::widget::PaneGrid + /// [`PaneGrid`]: super::PaneGrid Dragging { /// The [`Pane`] being dragged. pane: Pane, @@ -412,7 +414,7 @@ pub enum Action { }, /// A [`Split`] in the [`PaneGrid`] is being dragged. /// - /// [`PaneGrid`]: crate::widget::PaneGrid + /// [`PaneGrid`]: super::PaneGrid Resizing { /// The [`Split`] being dragged. split: Split, diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index cac24e68..47457337 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -11,7 +11,7 @@ use crate::core::{ /// The title bar of a [`Pane`]. /// -/// [`Pane`]: crate::widget::pane_grid::Pane +/// [`Pane`]: super::Pane #[allow(missing_debug_implementations)] pub struct TitleBar<'a, Message, Renderer = crate::Renderer> where @@ -75,7 +75,7 @@ where /// [`TitleBar`] is hovered. /// /// [`controls`]: Self::controls - /// [`Pane`]: crate::widget::pane_grid::Pane + /// [`Pane`]: super::Pane pub fn always_show_controls(mut self) -> Self { self.always_show_controls = true; self @@ -114,7 +114,7 @@ where /// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`]. /// - /// [`Renderer`]: crate::Renderer + /// [`Renderer`]: crate::core::Renderer pub fn draw( &self, tree: &Tree, diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 0a1e2a99..26fe5ca2 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -105,7 +105,7 @@ where self } - /// Sets the text [`LineHeight`] of the [`PickList`]. + /// Sets the text [`text::LineHeight`] of the [`PickList`]. pub fn text_line_height( mut self, line_height: impl Into, diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 65d71ec2..12bbd9c7 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -156,7 +156,7 @@ where self } - /// Sets the text [`LineHeight`] of the [`Radio`] button. + /// Sets the text [`text::LineHeight`] of the [`Radio`] button. pub fn text_line_height( mut self, line_height: impl Into, diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 61fc0055..f7a90880 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -182,7 +182,7 @@ where self } - /// Sets the [`LineHeight`] of the [`TextInput`]. + /// Sets the [`text::LineHeight`] of the [`TextInput`]. pub fn line_height( mut self, line_height: impl Into, diff --git a/widget/src/text_input/value.rs b/widget/src/text_input/value.rs index cf4da562..d1b056c8 100644 --- a/widget/src/text_input/value.rs +++ b/widget/src/text_input/value.rs @@ -2,7 +2,7 @@ use unicode_segmentation::UnicodeSegmentation; /// The value of a [`TextInput`]. /// -/// [`TextInput`]: crate::widget::TextInput +/// [`TextInput`]: super::TextInput // TODO: Reduce allocations, cache results (?) #[derive(Debug, Clone)] pub struct Value { diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index c8187181..3793f5b0 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -109,7 +109,7 @@ where self } - /// Sets the text [`LineHeight`] of the [`Toggler`]. + /// Sets the text [`text::LineHeight`] of the [`Toggler`]. pub fn text_line_height( mut self, line_height: impl Into, @@ -136,9 +136,9 @@ where self } - /// Sets the [`Font`] of the text of the [`Toggler`] + /// Sets the [`Renderer::Font`] of the text of the [`Toggler`] /// - /// [`Font`]: crate::text::Renderer::Font + /// [`Renderer::Font`]: crate::core::text::Renderer pub fn font(mut self, font: impl Into) -> Self { self.font = Some(font.into()); self diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index 967f43f2..e655529a 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -184,9 +184,7 @@ where /// window. /// /// Normally an [`Application`] should be synchronized with its [`State`] - /// and window after calling [`Application::update`]. - /// - /// [`Application::update`]: crate::Program::update + /// and window after calling [`crate::application::update`]. pub fn synchronize(&mut self, application: &A, window: &Window) { // Update window title let new_title = application.title(); diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index b2398e62..3ecd044c 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -521,7 +521,7 @@ pub fn user_attention( } } -/// Converts some [`Icon`] into it's `winit` counterpart. +/// Converts some [`window::Icon`] into it's `winit` counterpart. /// /// Returns `None` if there is an error during the conversion. pub fn icon(icon: window::Icon) -> Option { -- cgit From f60884f6f8639f75258c264bf4a15591351ef05b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 9 Sep 2023 20:58:45 +0200 Subject: Deny `broken_intradoc_links` and verify documentation in CI --- .github/workflows/document.yml | 6 ++---- core/src/lib.rs | 5 +++-- futures/src/lib.rs | 5 +++-- graphics/src/lib.rs | 5 +++-- renderer/src/lib.rs | 13 +++++++++++++ runtime/src/lib.rs | 5 +++-- src/lib.rs | 5 +++-- style/src/lib.rs | 8 +++++--- tiny_skia/src/geometry.rs | 2 +- tiny_skia/src/lib.rs | 13 +++++++++++++ tiny_skia/src/raster.rs | 4 ++-- tiny_skia/src/text.rs | 6 +++--- tiny_skia/src/vector.rs | 8 ++++---- tiny_skia/src/window/compositor.rs | 1 + wgpu/src/lib.rs | 5 +++-- widget/src/lib.rs | 5 +++-- winit/src/lib.rs | 5 +++-- 17 files changed, 68 insertions(+), 33 deletions(-) diff --git a/.github/workflows/document.yml b/.github/workflows/document.yml index 09a7a4d5..230c5cb0 100644 --- a/.github/workflows/document.yml +++ b/.github/workflows/document.yml @@ -1,8 +1,5 @@ name: Document -on: - push: - branches: - - master +on: [push, pull_request] jobs: all: runs-on: ubuntu-20.04 @@ -31,6 +28,7 @@ jobs: - name: Write CNAME file run: echo 'docs.iced.rs' > ./target/doc/CNAME - name: Publish documentation + if: github.ref == 'refs/heads/master' uses: peaceiris/actions-gh-pages@v3 with: deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }} diff --git a/core/src/lib.rs b/core/src/lib.rs index c1c8424b..1bfba7bd 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -9,6 +9,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] +#![forbid(unsafe_code, rust_2018_idioms)] #![deny( missing_debug_implementations, missing_docs, @@ -17,9 +18,9 @@ clippy::from_over_into, clippy::needless_borrow, clippy::new_without_default, - clippy::useless_conversion + clippy::useless_conversion, + rustdoc::broken_intra_doc_links )] -#![forbid(unsafe_code, rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] pub mod alignment; pub mod clipboard; diff --git a/futures/src/lib.rs b/futures/src/lib.rs index 8e75331f..abc46176 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -4,6 +4,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] +#![forbid(unsafe_code, rust_2018_idioms)] #![deny( missing_debug_implementations, missing_docs, @@ -12,9 +13,9 @@ clippy::from_over_into, clippy::needless_borrow, clippy::new_without_default, - clippy::useless_conversion + clippy::useless_conversion, + rustdoc::broken_intra_doc_links )] -#![forbid(unsafe_code, rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub use futures; diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index af374a2f..4e814eb0 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -7,6 +7,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] +#![forbid(rust_2018_idioms)] #![deny( missing_debug_implementations, missing_docs, @@ -16,9 +17,9 @@ clippy::from_over_into, clippy::needless_borrow, clippy::new_without_default, - clippy::useless_conversion + clippy::useless_conversion, + rustdoc::broken_intra_doc_links )] -#![forbid(rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] mod antialiasing; diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 7d1a02c2..6dcc0e96 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -1,3 +1,16 @@ +#![forbid(rust_2018_idioms)] +#![deny( + unsafe_code, + unused_results, + clippy::extra_unused_lifetimes, + clippy::from_over_into, + clippy::needless_borrow, + clippy::new_without_default, + clippy::useless_conversion, + rustdoc::broken_intra_doc_links +)] +#![allow(clippy::inherent_to_string, clippy::type_complexity)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod compositor; #[cfg(feature = "geometry")] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index dc631d47..c3261cca 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -8,6 +8,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] +#![forbid(unsafe_code, rust_2018_idioms)] #![deny( missing_debug_implementations, missing_docs, @@ -16,9 +17,9 @@ clippy::from_over_into, clippy::needless_borrow, clippy::new_without_default, - clippy::useless_conversion + clippy::useless_conversion, + rustdoc::broken_intra_doc_links )] -#![forbid(unsafe_code, rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod clipboard; pub mod command; diff --git a/src/lib.rs b/src/lib.rs index 8780d276..3cbe716a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -151,6 +151,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] +#![forbid(rust_2018_idioms, unsafe_code)] #![deny( missing_debug_implementations, missing_docs, @@ -159,9 +160,9 @@ clippy::from_over_into, clippy::needless_borrow, clippy::new_without_default, - clippy::useless_conversion + clippy::useless_conversion, + rustdoc::broken_intra_doc_links )] -#![forbid(rust_2018_idioms, unsafe_code)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] use iced_widget::graphics; diff --git a/style/src/lib.rs b/style/src/lib.rs index 286ff9db..0c555ed8 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -7,16 +7,18 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] +#![forbid(unsafe_code, rust_2018_idioms)] #![deny( unused_results, clippy::extra_unused_lifetimes, clippy::from_over_into, clippy::needless_borrow, clippy::new_without_default, - clippy::useless_conversion + clippy::useless_conversion, + missing_docs, + unused_results, + rustdoc::broken_intra_doc_links )] -#![deny(missing_docs, unused_results)] -#![forbid(unsafe_code, rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] pub use iced_core as core; diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 1df5aa18..047bc0ff 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -303,7 +303,7 @@ pub fn into_fill_rule(rule: fill::Rule) -> tiny_skia::FillRule { } } -pub fn into_stroke(stroke: &Stroke) -> tiny_skia::Stroke { +pub fn into_stroke(stroke: &Stroke<'_>) -> tiny_skia::Stroke { tiny_skia::Stroke { width: stroke.width, line_cap: match stroke.line_cap { diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index 15de6ce2..e48468e9 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -1,3 +1,16 @@ +#![forbid(rust_2018_idioms)] +#![deny( + unsafe_code, + unused_results, + clippy::extra_unused_lifetimes, + clippy::from_over_into, + clippy::needless_borrow, + clippy::new_without_default, + clippy::useless_conversion, + rustdoc::broken_intra_doc_links +)] +#![allow(clippy::inherent_to_string, clippy::type_complexity)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod window; mod backend; diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index dedb127c..d13b1167 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -85,14 +85,14 @@ impl Cache { ); } - entry.insert(Some(Entry { + let _ = entry.insert(Some(Entry { width: image.width(), height: image.height(), pixels: buffer, })); } - self.hits.insert(id); + let _ = self.hits.insert(id); self.entries.get(&id).unwrap().as_ref().map(|entry| { tiny_skia::PixmapRef::from_bytes( bytemuck::cast_slice(&entry.pixels), diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 306b400a..dabbe952 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -32,7 +32,7 @@ impl Pipeline { } pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - self.font_system.get_mut().db_mut().load_font_source( + let _ = self.font_system.get_mut().db_mut().load_font_source( cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())), ); @@ -335,10 +335,10 @@ impl GlyphCache { } } - entry.insert((buffer, image.placement)); + let _ = entry.insert((buffer, image.placement)); } - self.recently_used.insert(key); + let _ = self.recently_used.insert(key); self.entries.get(&key).map(|(buffer, placement)| { (bytemuck::cast_slice(buffer.as_slice()), *placement) diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index 433ca0f5..490b9f69 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -92,10 +92,10 @@ impl Cache { } }; - entry.insert(svg); + let _ = entry.insert(svg); } - self.tree_hits.insert(id); + let _ = self.tree_hits.insert(id); self.trees.get(&id).unwrap().as_ref() } @@ -178,10 +178,10 @@ impl Cache { } } - self.rasters.insert(key, image); + let _ = self.rasters.insert(key, image); } - self.raster_hits.insert(key); + let _ = self.raster_hits.insert(key); self.rasters.get(&key).map(tiny_skia::Pixmap::as_ref) } diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 775cf9e5..c0da5142 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -39,6 +39,7 @@ impl crate::graphics::Compositor for Compositor { width: u32, height: u32, ) -> Surface { + #[allow(unsafe_code)] let window = unsafe { softbuffer::GraphicsContext::new(window, window) } .expect("Create softbuffer for window"); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index d1e4b7af..b9f54560 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -20,6 +20,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] +#![forbid(rust_2018_idioms)] #![deny( missing_debug_implementations, missing_docs, @@ -29,9 +30,9 @@ clippy::from_over_into, clippy::needless_borrow, clippy::new_without_default, - clippy::useless_conversion + clippy::useless_conversion, + rustdoc::broken_intra_doc_links )] -#![forbid(rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod layer; diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 316e8829..5cb0f8de 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -2,6 +2,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] +#![forbid(unsafe_code, rust_2018_idioms)] #![deny( missing_debug_implementations, missing_docs, @@ -10,9 +11,9 @@ clippy::from_over_into, clippy::needless_borrow, clippy::new_without_default, - clippy::useless_conversion + clippy::useless_conversion, + rustdoc::broken_intra_doc_links )] -#![forbid(unsafe_code, rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub use iced_renderer as renderer; diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 1a87ca11..9852a182 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -17,6 +17,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] +#![forbid(rust_2018_idioms)] #![deny( missing_debug_implementations, missing_docs, @@ -26,9 +27,9 @@ clippy::needless_borrow, clippy::new_without_default, clippy::useless_conversion, - unsafe_code + unsafe_code, + rustdoc::broken_intra_doc_links )] -#![forbid(rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub use iced_graphics as graphics; -- cgit From 419d9374b79b39293ba9a17967c2356d29377d8f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 9 Sep 2023 21:08:23 +0200 Subject: Fix outstanding broken intradoc links --- core/src/gradient.rs | 2 -- graphics/src/compositor.rs | 2 +- graphics/src/geometry/fill.rs | 4 +++- graphics/src/geometry/stroke.rs | 4 +++- graphics/src/gradient.rs | 7 +------ 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/core/src/gradient.rs b/core/src/gradient.rs index 576c9e4d..4a0d5ea0 100644 --- a/core/src/gradient.rs +++ b/core/src/gradient.rs @@ -6,8 +6,6 @@ use std::cmp::Ordering; #[derive(Debug, Clone, Copy, PartialEq)] /// A fill which transitions colors progressively along a direction, either linearly, radially (TBD), /// or conically (TBD). -/// -/// For a gradient which can be used as a fill on a canvas, see [`iced_graphics::Gradient`]. pub enum Gradient { /// A linear gradient interpolates colors along a direction at a specific angle. Linear(Linear), diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index f7b86045..7173ffa7 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -63,7 +63,7 @@ pub trait Compositor: Sized { /// Screenshots the current [`Renderer`] primitives to an offscreen texture, and returns the bytes of /// the texture ordered as `RGBA` in the sRGB color space. /// - /// [`Renderer`]: Self::Renderer; + /// [`Renderer`]: Self::Renderer fn screenshot>( &mut self, renderer: &mut Self::Renderer, diff --git a/graphics/src/geometry/fill.rs b/graphics/src/geometry/fill.rs index b773c99b..670fbc12 100644 --- a/graphics/src/geometry/fill.rs +++ b/graphics/src/geometry/fill.rs @@ -1,4 +1,6 @@ -//! Fill [crate::widget::canvas::Geometry] with a certain style. +//! Fill [`Geometry`] with a certain style. +//! +//! [`Geometry`]: super::Renderer::Geometry pub use crate::geometry::Style; use crate::core::Color; diff --git a/graphics/src/geometry/stroke.rs b/graphics/src/geometry/stroke.rs index 69a76e1c..aff49ab3 100644 --- a/graphics/src/geometry/stroke.rs +++ b/graphics/src/geometry/stroke.rs @@ -1,4 +1,6 @@ -//! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles. +//! Create lines from a [`Path`] and assigns them various attributes/styles. +//! +//! [`Path`]: super::Path pub use crate::geometry::Style; use iced_core::Color; diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs index f5f5f8b4..b274ec86 100644 --- a/graphics/src/gradient.rs +++ b/graphics/src/gradient.rs @@ -1,8 +1,6 @@ //! A gradient that can be used as a fill for some geometry. //! //! For a gradient that you can use as a background variant for a widget, see [`Gradient`]. -//! -//! [`Gradient`]: crate::core::Gradient; use crate::color; use crate::core::gradient::ColorStop; use crate::core::{self, Color, Point, Rectangle}; @@ -36,10 +34,7 @@ impl Gradient { } } -/// A linear gradient that can be used in the style of [`Fill`] or [`Stroke`]. -/// -/// [`Fill`]: crate::geometry::Fill; -/// [`Stroke`]: crate::geometry::Stroke; +/// A linear gradient. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Linear { /// The absolute starting position of the gradient. -- cgit From fbc9ef74c406b2a58bb2a093262323e346faf485 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 9 Sep 2023 22:42:41 +0200 Subject: Change init_with_level to init in examples --- examples/integration/Cargo.toml | 1 - examples/integration/src/main.rs | 2 +- examples/tour/Cargo.toml | 1 - examples/tour/src/main.rs | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml index 6b9fcc57..a4a961f8 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -19,6 +19,5 @@ iced_wgpu.features = ["webgl"] console_error_panic_hook = "0.1" console_log = "1.0" -log.workspace = true wasm-bindgen = "0.2" web-sys = { version = "0.3", features = ["Element", "HtmlCanvasElement", "Window", "Document"] } diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index af48af5f..d0b2d891 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -29,7 +29,7 @@ use winit::platform::web::WindowBuilderExtWebSys; pub fn main() -> Result<(), Box> { #[cfg(target_arch = "wasm32")] let canvas_element = { - console_log::init_with_level(log::Level::Debug)?; + console_log::init().expect("Initialize logger"); std::panic::set_hook(Box::new(console_error_panic_hook::hook)); diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index 11920e0d..9e984ad1 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -18,4 +18,3 @@ iced.features = ["image", "debug", "webgl"] console_error_panic_hook = "0.1" console_log = "1.0" -log.workspace = true diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index f0e3c0cd..3387b481 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -10,7 +10,7 @@ use iced::{Color, Element, Font, Length, Renderer, Sandbox, Settings}; pub fn main() -> iced::Result { #[cfg(target_arch = "wasm32")] { - console_log::init_with_level(log::Level::Debug).expect("Initialize logger"); + console_log::init().expect("Initialize logger"); std::panic::set_hook(Box::new(console_error_panic_hook::hook)); } -- cgit From bbb9c2d92819f2ff96e51773037138fef488290a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 9 Sep 2023 23:05:44 +0200 Subject: Count grapheme clusters in `Paragraph::grapheme_position` --- graphics/src/text/paragraph.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index cd12bc8f..e4350cff 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -189,8 +189,27 @@ impl core::text::Paragraph for Paragraph { fn grapheme_position(&self, line: usize, index: usize) -> Option { let run = self.internal().buffer.layout_runs().nth(line)?; - // TODO: Index represents a grapheme, not a glyph - let glyph = run.glyphs.get(index).or_else(|| run.glyphs.last())?; + // index represents a grapheme, not a glyph + // Let's find the first glyph for the given grapheme cluster + let mut last_start = None; + let mut graphemes_seen = 0; + + let glyph = run + .glyphs + .iter() + .find(|glyph| { + if graphemes_seen == index { + return true; + } + + if Some(glyph.start) != last_start { + last_start = Some(glyph.start); + graphemes_seen += 1; + } + + false + }) + .or_else(|| run.glyphs.last())?; let advance_last = if index == run.glyphs.len() { glyph.w -- cgit From 956512338905bac0b156fdaf16fe3c3e07e97a84 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 9 Sep 2023 23:06:06 +0200 Subject: Remove unused `unicode-segmentation` dependency from `iced_graphics` --- graphics/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 442eb007..6fc6ab00 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -25,7 +25,6 @@ thiserror = "1.0" bitflags = "1.2" cosmic-text = "0.9" rustc-hash = "1.1" -unicode-segmentation = "1.6" [dependencies.bytemuck] version = "1.4" -- cgit From 8aa7874ba9ba4a0e7cafb9a447a3db92e95aeb87 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 10 Sep 2023 00:43:38 +0200 Subject: Fix Wasm build of `todos` example --- examples/todos/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 5d8936df..501bf67e 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -17,6 +17,7 @@ use uuid::Uuid; static INPUT_ID: Lazy = Lazy::new(text_input::Id::unique); pub fn main() -> iced::Result { + #[cfg(not(target_arch = "wasm32"))] tracing_subscriber::fmt::init(); Todos::run(Settings { -- cgit From c6928457440319af3e2cf482b9e916bad9f277a5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 10 Sep 2023 00:44:58 +0200 Subject: Fix broken intradoc link in `widget::keyed` module --- widget/src/keyed.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/widget/src/keyed.rs b/widget/src/keyed.rs index 55019535..ad531e66 100644 --- a/widget/src/keyed.rs +++ b/widget/src/keyed.rs @@ -42,8 +42,6 @@ pub mod column; pub use column::Column; /// Creates a [`Column`] with the given children. -/// -/// [`Column`]: widget::Column #[macro_export] macro_rules! keyed_column { () => ( -- cgit From b42b24b79a097aab10006490cd2ec8a2332fd5d0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 10 Sep 2023 00:55:46 +0200 Subject: Fix (more) broken intradoc links Good thing I just set up CI earlier for this :sweat_smile: --- core/src/text.rs | 2 +- graphics/src/backend.rs | 2 +- graphics/src/primitive.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/text.rs b/core/src/text.rs index f5e9abc4..0e3617b1 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -215,7 +215,7 @@ pub trait Paragraph: Default { /// Returns the [`LineHeight`] of the [`Paragraph`]. fn line_height(&self) -> LineHeight; - /// Returns the [`Font`] of the [`Paragraph`]. + /// Returns the [`Self::Font`] of the [`Paragraph`]. fn font(&self) -> Self::Font; /// Returns the [`Shaping`] strategy of the [`Paragraph`]. diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 6774b9ca..c2ac82ba 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -16,7 +16,7 @@ pub trait Backend { /// A graphics backend that supports text rendering. pub trait Text { - /// Loads a [`Font`] from its bytes. + /// Loads a font from its bytes. fn load_font(&mut self, font: Cow<'static, [u8]>); /// Returns the [`cosmic_text::FontSystem`] of the [`Backend`]. diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index cdc8923e..8a97e6e7 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -34,11 +34,11 @@ pub enum Primitive { }, /// A paragraph primitive Paragraph { - /// The [`Paragraph`]. + /// The [`paragraph::Weak`] reference. paragraph: paragraph::Weak, - /// The position of the [`Paragraph`]. + /// The position of the paragraph. position: Point, - /// The color of the [`Paragraph`]. + /// The color of the paragraph. color: Color, }, /// A quad primitive -- cgit From 1cc5bf59d7c4f47ae47d9a4e22ebaab3ea4975c1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 10 Sep 2023 00:59:54 +0200 Subject: Fix `CHANGELOG` --- CHANGELOG.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed02baf7..fdd832e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Explicit text caching. [#2058](https://github.com/iced-rs/iced/pull/2058) - `Theme::Custom::with_fn` for custom extended palette generation. [#2067](https://github.com/iced-rs/iced/pull/2067) ### Changed @@ -20,13 +21,6 @@ Many thanks to... - @akshayr-mecha - @dtzxporter -### Added -- Explicit text caching. [#2058](https://github.com/iced-rs/iced/pull/2058) - -Many thanks to... - -- Add your name here - ## [0.10.0] - 2023-07-28 ### Added - Text shaping, font fallback, and `iced_wgpu` overhaul. [#1697](https://github.com/iced-rs/iced/pull/1697) -- cgit From df72fd1095baf76357e9fe04df7b66b643509f42 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 10 Sep 2023 03:04:02 +0200 Subject: Fix `Widget::layout` implementation of `MouseArea` --- widget/src/mouse_area.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 65d44dd5..3a5b01a3 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -124,7 +124,9 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.content.as_widget().layout(tree, renderer, limits) + self.content + .as_widget() + .layout(&mut tree.children[0], renderer, limits) } fn operate( -- cgit From b329950a4244c572114288431ad6beca558124ef Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 10 Sep 2023 03:15:31 +0200 Subject: Fix `Tooltip` widget state management --- widget/src/tooltip.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 534d901a..b889f8db 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -139,7 +139,9 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.content.as_widget().layout(tree, renderer, limits) + self.content + .as_widget() + .layout(&mut tree.children[0], renderer, limits) } fn on_event( @@ -434,7 +436,7 @@ where Widget::<(), Renderer>::draw( self.tooltip, - &widget::Tree::empty(), + &self.state, renderer, theme, &defaults, -- cgit From 6d379b7fcef817bee1b1cb367bd308f9c6655b0b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 10 Sep 2023 03:16:55 +0200 Subject: Fix unnecessary dereference in `Tooltip` --- widget/src/tooltip.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index b889f8db..edc74e31 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -436,7 +436,7 @@ where Widget::<(), Renderer>::draw( self.tooltip, - &self.state, + self.state, renderer, theme, &defaults, -- cgit From bc1bde0d5ca1ec291f13e108f1543daa75b97848 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 10 Sep 2023 03:36:31 +0200 Subject: Fix `ComboBox` widget not displaying selection text --- widget/src/combo_box.rs | 25 +++++++++++++++++++++++-- widget/src/text_input.rs | 28 +++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index d6915281..044dc0ef 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -311,7 +311,20 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.text_input.layout(tree, renderer, limits) + let is_focused = { + let text_input_state = tree.children[0] + .state + .downcast_ref::>(); + + text_input_state.is_focused() + }; + + self.text_input.layout( + &mut tree.children[0], + renderer, + limits, + (!is_focused).then_some(&self.selection), + ) } fn tag(&self) -> widget::tree::Tag { @@ -665,7 +678,15 @@ where menu, &filtered_options.options, hovered_option, - |x| (self.on_selected)(x), + |x| { + tree.children[0] + .state + .downcast_mut::>( + ) + .unfocus(); + + (self.on_selected)(x) + }, self.on_option_hovered.as_deref(), ) .width(bounds.width) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index bfd196fd..7d5ae806 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -200,6 +200,32 @@ where self } + /// Lays out the [`TextInput`], overriding its [`Value`] if provided. + /// + /// [`Renderer`]: text::Renderer + pub fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + value: Option<&Value>, + ) -> layout::Node { + layout( + renderer, + limits, + self.width, + self.padding, + self.size, + self.font, + self.line_height, + self.icon.as_ref(), + tree.state.downcast_mut::>(), + value.unwrap_or(&self.value), + &self.placeholder, + self.is_secure, + ) + } + /// Draws the [`TextInput`] with the given [`Renderer`], overriding its /// [`Value`] if provided. /// @@ -1411,7 +1437,7 @@ fn find_cursor_position( Some( unicode_segmentation::UnicodeSegmentation::graphemes( - &value[..char_offset], + &value[..char_offset.min(value.len())], true, ) .count(), -- cgit From 90bd581d8ed72ac1a927a90ba04ebdd156fa2922 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 10 Sep 2023 10:18:58 +0200 Subject: Fix `ComboBox` widget panic on wasm --- widget/src/combo_box.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 044dc0ef..768c2402 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -6,6 +6,7 @@ use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::text; +use crate::core::time::Instant; use crate::core::widget::{self, Widget}; use crate::core::{Clipboard, Element, Length, Padding, Rectangle, Shell}; use crate::overlay::menu; @@ -14,7 +15,6 @@ 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. /// -- cgit From 9245423c5d82f88c99adecaaf5dd2ac3559a05a8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 11 Sep 2023 01:10:31 +0200 Subject: Fix link in `CONTRIBUTING` guide --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c01acc4e..4e7075c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,6 @@ Besides directly writing code, there are many other different ways you can contr [the ecosystem overview]: ECOSYSTEM.md [the roadmap]: ROADMAP.md -[our Discourse fourm]: https://discourse.iced.rs/ -[our Discord server]: https://discord.gg/3xZJ65GAhd +[our Discourse forum]: https://discourse.iced.rs/ [Code is the Easy Part]: https://youtu.be/DSjbTC-hvqQ?t=1138 [The Hard Parts of Open Source]: https://www.youtube.com/watch?v=o_4EX4dPppA -- cgit From 346af3f8b0baa418fd37b878bc2930ff0bd57cc0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 11 Sep 2023 02:47:24 +0200 Subject: Make `FontSystem` global and simplify `Paragraph` API --- core/src/renderer/null.rs | 34 ++-------- core/src/text.rs | 90 +++++++------------------ core/src/widget/text.rs | 23 +++---- graphics/Cargo.toml | 12 ++-- graphics/src/backend.rs | 4 -- graphics/src/renderer.rs | 35 ---------- graphics/src/text.rs | 47 ++++++------- graphics/src/text/paragraph.rs | 147 +++++++++++++++++++---------------------- renderer/src/lib.rs | 16 ----- tiny_skia/src/backend.rs | 5 -- tiny_skia/src/text.rs | 21 +++--- wgpu/src/backend.rs | 5 -- wgpu/src/text.rs | 17 +++-- widget/src/pick_list.rs | 22 +++--- widget/src/text_input.rs | 17 ++--- 15 files changed, 166 insertions(+), 329 deletions(-) diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index 55d58a59..0ffd3649 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -58,16 +58,6 @@ impl text::Renderer for Null { fn load_font(&mut self, _font: Cow<'static, [u8]>) {} - fn create_paragraph(&self, _text: Text<'_, Self::Font>) -> Self::Paragraph { - } - - fn resize_paragraph( - &self, - _paragraph: &mut Self::Paragraph, - _new_bounds: Size, - ) { - } - fn fill_paragraph( &mut self, _paragraph: &Self::Paragraph, @@ -88,24 +78,12 @@ impl text::Renderer for Null { impl text::Paragraph for () { type Font = Font; - fn content(&self) -> &str { - "" - } - - fn text_size(&self) -> Pixels { - Pixels(16.0) - } - - fn font(&self) -> Self::Font { - Font::default() - } + fn with_text(_text: Text<'_, Self::Font>) -> Self {} - fn line_height(&self) -> text::LineHeight { - text::LineHeight::default() - } + fn resize(&mut self, _new_bounds: Size) {} - fn shaping(&self) -> text::Shaping { - text::Shaping::default() + fn compare(&self, _text: Text<'_, Self::Font>) -> text::Difference { + text::Difference::None } fn horizontal_alignment(&self) -> alignment::Horizontal { @@ -120,10 +98,6 @@ impl text::Paragraph for () { None } - fn bounds(&self) -> Size { - Size::ZERO - } - fn min_bounds(&self) -> Size { Size::ZERO } diff --git a/core/src/text.rs b/core/src/text.rs index 0e3617b1..ff85696e 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -156,33 +156,6 @@ pub trait Renderer: crate::Renderer { /// Loads a [`Self::Font`] from its bytes. fn load_font(&mut self, font: Cow<'static, [u8]>); - /// Creates a new [`Paragraph`] laid out with the given [`Text`]. - fn create_paragraph(&self, text: Text<'_, Self::Font>) -> Self::Paragraph; - - /// Lays out the given [`Paragraph`] with some new boundaries. - fn resize_paragraph( - &self, - paragraph: &mut Self::Paragraph, - new_bounds: Size, - ); - - /// Updates a [`Paragraph`] to match the given [`Text`], if needed. - fn update_paragraph( - &self, - paragraph: &mut Self::Paragraph, - text: Text<'_, Self::Font>, - ) { - match compare(paragraph, text) { - Difference::None => {} - Difference::Bounds => { - self.resize_paragraph(paragraph, text.bounds); - } - Difference::Shape => { - *paragraph = self.create_paragraph(text); - } - } - } - /// Draws the given [`Paragraph`] at the given position and with the given /// [`Color`]. fn fill_paragraph( @@ -201,25 +174,21 @@ pub trait Renderer: crate::Renderer { color: Color, ); } + /// A text paragraph. -pub trait Paragraph: Default { +pub trait Paragraph: Sized + Default { /// The font of this [`Paragraph`]. - type Font; - - /// Returns the content of the [`Paragraph`]. - fn content(&self) -> &str; - - /// Returns the text size of the [`Paragraph`]. - fn text_size(&self) -> Pixels; + type Font: Copy + PartialEq; - /// Returns the [`LineHeight`] of the [`Paragraph`]. - fn line_height(&self) -> LineHeight; + /// Creates a new [`Paragraph`] laid out with the given [`Text`]. + fn with_text(text: Text<'_, Self::Font>) -> Self; - /// Returns the [`Self::Font`] of the [`Paragraph`]. - fn font(&self) -> Self::Font; + /// Lays out the [`Paragraph`] with some new boundaries. + fn resize(&mut self, new_bounds: Size); - /// Returns the [`Shaping`] strategy of the [`Paragraph`]. - fn shaping(&self) -> Shaping; + /// Compares the [`Paragraph`] with some desired [`Text`] and returns the + /// [`Difference`]. + fn compare(&self, text: Text<'_, Self::Font>) -> Difference; /// Returns the horizontal alignment of the [`Paragraph`]. fn horizontal_alignment(&self) -> alignment::Horizontal; @@ -227,9 +196,6 @@ pub trait Paragraph: Default { /// Returns the vertical alignment of the [`Paragraph`]. fn vertical_alignment(&self) -> alignment::Vertical; - /// Returns the boundaries of the [`Paragraph`]. - fn bounds(&self) -> Size; - /// Returns the minimum boundaries that can fit the contents of the /// [`Paragraph`]. fn min_bounds(&self) -> Size; @@ -241,6 +207,19 @@ pub trait Paragraph: Default { /// Returns the distance to the given grapheme index in the [`Paragraph`]. fn grapheme_position(&self, line: usize, index: usize) -> Option; + /// Updates the [`Paragraph`] to match the given [`Text`], if needed. + fn update(&mut self, text: Text<'_, Self::Font>) { + match self.compare(text) { + Difference::None => {} + Difference::Bounds => { + self.resize(text.bounds); + } + Difference::Shape => { + *self = Self::with_text(text); + } + } + } + /// Returns the minimum width that can fit the contents of the [`Paragraph`]. fn min_width(&self) -> f32 { self.min_bounds().width @@ -276,26 +255,3 @@ pub enum Difference { /// the text is necessary. Shape, } - -/// Compares a [`Paragraph`] with some desired [`Text`] and returns the -/// [`Difference`]. -pub fn compare( - paragraph: &impl Paragraph, - text: Text<'_, Font>, -) -> Difference { - if paragraph.content() != text.content - || paragraph.text_size() != text.size - || paragraph.line_height().to_absolute(text.size) - != text.line_height.to_absolute(text.size) - || paragraph.font() != text.font - || paragraph.shaping() != text.shaping - || paragraph.horizontal_alignment() != text.horizontal_alignment - || paragraph.vertical_alignment() != text.vertical_alignment - { - Difference::Shape - } else if paragraph.bounds() != text.bounds { - Difference::Bounds - } else { - Difference::None - } -} diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 53ed463e..c7c9f539 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -212,19 +212,16 @@ where let State(ref mut paragraph) = state; - renderer.update_paragraph( - paragraph, - text::Text { - content, - bounds, - size, - line_height, - font, - shaping, - horizontal_alignment, - vertical_alignment, - }, - ); + paragraph.update(text::Text { + content, + bounds, + size, + line_height, + font, + shaping, + horizontal_alignment, + vertical_alignment, + }); let size = limits.resolve(paragraph.min_bounds()); diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index ff698649..26bd1435 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -25,16 +25,15 @@ iced_core.workspace = true bitflags.workspace = true bytemuck.workspace = true +cosmic-text.workspace = true glam.workspace = true half.workspace = true log.workspace = true +once_cell.workspace = true raw-window-handle.workspace = true -thiserror.workspace = true -cosmic-text.workspace = true rustc-hash.workspace = true - -lyon_path.workspace = true -lyon_path.optional = true +thiserror.workspace = true +twox-hash.workspace = true image.workspace = true image.optional = true @@ -42,7 +41,8 @@ image.optional = true kamadak-exif.workspace = true kamadak-exif.optional = true -twox-hash.workspace = true +lyon_path.workspace = true +lyon_path.optional = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] twox-hash.workspace = true diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index c2ac82ba..10eb337f 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -2,7 +2,6 @@ use crate::core::image; use crate::core::svg; use crate::core::Size; -use crate::text; use std::borrow::Cow; @@ -18,9 +17,6 @@ pub trait Backend { pub trait Text { /// Loads a font from its bytes. fn load_font(&mut self, font: Cow<'static, [u8]>); - - /// Returns the [`cosmic_text::FontSystem`] of the [`Backend`]. - fn font_system(&self) -> &text::FontSystem; } /// A graphics backend that supports image rendering. diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index d4df29a5..c5033d36 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -158,41 +158,6 @@ where self.backend.load_font(bytes); } - fn create_paragraph(&self, text: Text<'_, Self::Font>) -> text::Paragraph { - text::Paragraph::with_text(text, self.backend.font_system()) - } - - fn update_paragraph( - &self, - paragraph: &mut Self::Paragraph, - text: Text<'_, Self::Font>, - ) { - let font_system = self.backend.font_system(); - - if paragraph.version() != font_system.version() { - // The font system has changed, paragraph fonts may be outdated - *paragraph = self.create_paragraph(text); - } else { - match core::text::compare(paragraph, text) { - core::text::Difference::None => {} - core::text::Difference::Bounds => { - self.resize_paragraph(paragraph, text.bounds); - } - core::text::Difference::Shape => { - *paragraph = self.create_paragraph(text); - } - } - } - } - - fn resize_paragraph( - &self, - paragraph: &mut Self::Paragraph, - new_bounds: Size, - ) { - paragraph.resize(new_bounds, self.backend.font_system()); - } - fn fill_paragraph( &mut self, paragraph: &Self::Paragraph, diff --git a/graphics/src/text.rs b/graphics/src/text.rs index bc06aa3c..f5ccaf52 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -10,40 +10,39 @@ use crate::core::font::{self, Font}; use crate::core::text::Shaping; use crate::core::Size; +use once_cell::sync::OnceCell; use std::borrow::Cow; -use std::sync::{self, Arc, RwLock}; +use std::sync::{Arc, RwLock}; -#[allow(missing_debug_implementations)] -pub struct FontSystem { - raw: RwLock, - version: Version, -} +pub fn font_system() -> &'static RwLock { + static FONT_SYSTEM: OnceCell> = OnceCell::new(); -impl FontSystem { - pub fn new() -> Self { - FontSystem { - raw: RwLock::new(cosmic_text::FontSystem::new_with_fonts( + FONT_SYSTEM.get_or_init(|| { + RwLock::new(FontSystem { + raw: cosmic_text::FontSystem::new_with_fonts( [cosmic_text::fontdb::Source::Binary(Arc::new( include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), ))] .into_iter(), - )), + ), version: Version::default(), - } - } + }) + }) +} - pub fn get_mut(&mut self) -> &mut cosmic_text::FontSystem { - self.raw.get_mut().expect("Lock font system") - } +#[allow(missing_debug_implementations)] +pub struct FontSystem { + raw: cosmic_text::FontSystem, + version: Version, +} - pub fn write( - &self, - ) -> (sync::RwLockWriteGuard<'_, cosmic_text::FontSystem>, Version) { - (self.raw.write().expect("Write font system"), self.version) +impl FontSystem { + pub fn raw(&mut self) -> &mut cosmic_text::FontSystem { + &mut self.raw } pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - let _ = self.get_mut().db_mut().load_font_source( + let _ = self.raw.db_mut().load_font_source( cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())), ); @@ -58,12 +57,6 @@ impl FontSystem { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct Version(u32); -impl Default for FontSystem { - fn default() -> Self { - Self::new() - } -} - pub fn measure(buffer: &cosmic_text::Buffer) -> Size { let (width, total_lines) = buffer .layout_runs() diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index e4350cff..d0396e8e 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -2,7 +2,7 @@ use crate::core; use crate::core::alignment; use crate::core::text::{Hit, LineHeight, Shaping, Text}; use crate::core::{Font, Pixels, Point, Size}; -use crate::text::{self, FontSystem}; +use crate::text; use std::fmt; use std::sync::{self, Arc}; @@ -27,13 +27,39 @@ impl Paragraph { Self::default() } - pub fn with_text(text: Text<'_, Font>, font_system: &FontSystem) -> Self { + pub fn buffer(&self) -> &cosmic_text::Buffer { + &self.internal().buffer + } + + pub fn downgrade(&self) -> Weak { + let paragraph = self.internal(); + + Weak { + raw: Arc::downgrade(paragraph), + min_bounds: paragraph.min_bounds, + horizontal_alignment: paragraph.horizontal_alignment, + vertical_alignment: paragraph.vertical_alignment, + } + } + + fn internal(&self) -> &Arc { + self.0 + .as_ref() + .expect("paragraph should always be initialized") + } +} + +impl core::text::Paragraph for Paragraph { + type Font = Font; + + fn with_text(text: Text<'_, Font>) -> Self { log::trace!("Allocating paragraph: {}", text.content); - let (mut font_system, version) = font_system.write(); + let mut font_system = + text::font_system().write().expect("Write font system"); let mut buffer = cosmic_text::Buffer::new( - &mut font_system, + font_system.raw(), cosmic_text::Metrics::new( text.size.into(), text.line_height.to_absolute(text.size).into(), @@ -41,13 +67,13 @@ impl Paragraph { ); buffer.set_size( - &mut font_system, + font_system.raw(), text.bounds.width, text.bounds.height, ); buffer.set_text( - &mut font_system, + font_system.raw(), text.content, text::to_attributes(text.font), text::to_shaping(text.shaping), @@ -64,30 +90,11 @@ impl Paragraph { shaping: text.shaping, bounds: text.bounds, min_bounds, - version, + version: font_system.version(), }))) } - pub fn buffer(&self) -> &cosmic_text::Buffer { - &self.internal().buffer - } - - pub fn version(&self) -> text::Version { - self.internal().version - } - - pub fn downgrade(&self) -> Weak { - let paragraph = self.internal(); - - Weak { - raw: Arc::downgrade(paragraph), - min_bounds: paragraph.min_bounds, - horizontal_alignment: paragraph.horizontal_alignment, - vertical_alignment: paragraph.vertical_alignment, - } - } - - pub fn resize(&mut self, new_bounds: Size, font_system: &FontSystem) { + fn resize(&mut self, new_bounds: Size) { let paragraph = self .0 .take() @@ -95,10 +102,11 @@ impl Paragraph { match Arc::try_unwrap(paragraph) { Ok(mut internal) => { - let (mut font_system, _) = font_system.write(); + let mut font_system = + text::font_system().write().expect("Write font system"); internal.buffer.set_size( - &mut font_system, + font_system.raw(), new_bounds.width, new_bounds.height, ); @@ -113,55 +121,42 @@ impl Paragraph { // If there is a strong reference somewhere, we recompute the // buffer from scratch - *self = Self::with_text( - Text { - content: &internal.content, - bounds: internal.bounds, - size: Pixels(metrics.font_size), - line_height: LineHeight::Absolute(Pixels( - metrics.line_height, - )), - font: internal.font, - horizontal_alignment: internal.horizontal_alignment, - vertical_alignment: internal.vertical_alignment, - shaping: internal.shaping, - }, - font_system, - ); + *self = Self::with_text(Text { + content: &internal.content, + bounds: internal.bounds, + size: Pixels(metrics.font_size), + line_height: LineHeight::Absolute(Pixels( + metrics.line_height, + )), + font: internal.font, + horizontal_alignment: internal.horizontal_alignment, + vertical_alignment: internal.vertical_alignment, + shaping: internal.shaping, + }); } } } - fn internal(&self) -> &Arc { - self.0 - .as_ref() - .expect("paragraph should always be initialized") - } -} - -impl core::text::Paragraph for Paragraph { - type Font = Font; - - fn content(&self) -> &str { - &self.internal().content - } - - fn text_size(&self) -> Pixels { - Pixels(self.internal().buffer.metrics().font_size) - } - - fn line_height(&self) -> LineHeight { - LineHeight::Absolute(Pixels( - self.internal().buffer.metrics().line_height, - )) - } - - fn font(&self) -> Font { - self.internal().font - } - - fn shaping(&self) -> Shaping { - self.internal().shaping + fn compare(&self, text: Text<'_, Font>) -> core::text::Difference { + let font_system = text::font_system().read().expect("Read font system"); + let paragraph = self.internal(); + let metrics = paragraph.buffer.metrics(); + + if paragraph.version != font_system.version + || paragraph.content != text.content + || metrics.font_size != text.size.0 + || metrics.line_height != text.line_height.to_absolute(text.size).0 + || paragraph.font != text.font + || paragraph.shaping != text.shaping + || paragraph.horizontal_alignment != text.horizontal_alignment + || paragraph.vertical_alignment != text.vertical_alignment + { + core::text::Difference::Shape + } else if paragraph.bounds != text.bounds { + core::text::Difference::Bounds + } else { + core::text::Difference::None + } } fn horizontal_alignment(&self) -> alignment::Horizontal { @@ -172,10 +167,6 @@ impl core::text::Paragraph for Paragraph { self.internal().vertical_alignment } - fn bounds(&self) -> Size { - self.internal().bounds - } - fn min_bounds(&self) -> Size { self.internal().min_bounds } diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 8bdf231d..73e56890 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -173,22 +173,6 @@ impl text::Renderer for Renderer { delegate!(self, renderer, renderer.default_size()) } - fn create_paragraph(&self, text: Text<'_, Self::Font>) -> Self::Paragraph { - delegate!(self, renderer, renderer.create_paragraph(text)) - } - - fn resize_paragraph( - &self, - paragraph: &mut Self::Paragraph, - new_bounds: Size, - ) { - delegate!( - self, - renderer, - renderer.resize_paragraph(paragraph, new_bounds) - ); - } - fn load_font(&mut self, bytes: Cow<'static, [u8]>) { delegate!(self, renderer, renderer.load_font(bytes)); } diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index c721d96e..72184c8a 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,6 +1,5 @@ use crate::core::{Background, Color, Gradient, Rectangle, Vector}; use crate::graphics::backend; -use crate::graphics::text; use crate::graphics::{Damage, Viewport}; use crate::primitive::{self, Primitive}; @@ -805,10 +804,6 @@ impl iced_graphics::Backend for Backend { } impl backend::Text for Backend { - fn font_system(&self) -> &text::FontSystem { - self.text_pipeline.font_system() - } - fn load_font(&mut self, font: Cow<'static, [u8]>) { self.text_pipeline.load_font(font); } diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index cb3ef54c..4f6e3941 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -2,8 +2,8 @@ use crate::core::alignment; use crate::core::text::{LineHeight, Shaping}; use crate::core::{Color, Font, Pixels, Point, Rectangle}; use crate::graphics::text::cache::{self, Cache}; +use crate::graphics::text::font_system; use crate::graphics::text::paragraph; -use crate::graphics::text::FontSystem; use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; @@ -12,7 +12,6 @@ use std::collections::hash_map; #[allow(missing_debug_implementations)] pub struct Pipeline { - font_system: FontSystem, glyph_cache: GlyphCache, cache: RefCell, } @@ -20,18 +19,16 @@ pub struct Pipeline { impl Pipeline { pub fn new() -> Self { Pipeline { - font_system: FontSystem::new(), glyph_cache: GlyphCache::new(), cache: RefCell::new(Cache::new()), } } - pub fn font_system(&self) -> &FontSystem { - &self.font_system - } - pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - self.font_system.load_font(bytes); + font_system() + .write() + .expect("Write font system") + .load_font(bytes); self.cache = RefCell::new(Cache::new()); } @@ -51,8 +48,10 @@ impl Pipeline { return; }; + let mut font_system = font_system().write().expect("Write font system"); + draw( - self.font_system.get_mut(), + font_system.raw(), &mut self.glyph_cache, paragraph.buffer(), Rectangle::new(position, paragraph.min_bounds()), @@ -82,7 +81,9 @@ impl Pipeline { ) { let line_height = f32::from(line_height.to_absolute(size)); - let font_system = self.font_system.get_mut(); + let mut font_system = font_system().write().expect("Write font system"); + let font_system = font_system.raw(); + let key = cache::Key { bounds: bounds.size(), content, diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 65c63f19..3d1755e1 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,5 +1,4 @@ use crate::core::{Color, Size}; -use crate::graphics; use crate::graphics::backend; use crate::graphics::color; use crate::graphics::{Transformation, Viewport}; @@ -310,10 +309,6 @@ impl crate::graphics::Backend for Backend { } impl backend::Text for Backend { - fn font_system(&self) -> &graphics::text::FontSystem { - self.text_pipeline.font_system() - } - fn load_font(&mut self, font: Cow<'static, [u8]>) { self.text_pipeline.load_font(font); } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index bd4f3e06..5c9f4d7e 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -2,7 +2,7 @@ use crate::core::alignment; use crate::core::{Rectangle, Size}; use crate::graphics::color; use crate::graphics::text::cache::{self, Cache}; -use crate::graphics::text::{FontSystem, Paragraph}; +use crate::graphics::text::{font_system, Paragraph}; use crate::layer::Text; use std::borrow::Cow; @@ -10,7 +10,6 @@ use std::cell::RefCell; #[allow(missing_debug_implementations)] pub struct Pipeline { - font_system: FontSystem, renderers: Vec, atlas: glyphon::TextAtlas, prepare_layer: usize, @@ -24,7 +23,6 @@ impl Pipeline { format: wgpu::TextureFormat, ) -> Self { Pipeline { - font_system: FontSystem::new(), renderers: Vec::new(), atlas: glyphon::TextAtlas::with_color_mode( device, @@ -41,12 +39,11 @@ impl Pipeline { } } - pub fn font_system(&self) -> &FontSystem { - &self.font_system - } - pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - self.font_system.load_font(bytes); + font_system() + .write() + .expect("Write font system") + .load_font(bytes); self.cache = RefCell::new(Cache::new()); } @@ -69,7 +66,9 @@ impl Pipeline { )); } - let font_system = self.font_system.get_mut(); + let mut font_system = font_system().write().expect("Write font system"); + let font_system = font_system.raw(); + let renderer = &mut self.renderers[self.prepare_layer]; let cache = self.cache.get_mut(); diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 056a5e65..4b89d6ff 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -415,23 +415,17 @@ where for (option, paragraph) in options.iter().zip(state.options.iter_mut()) { let label = option.to_string(); - renderer.update_paragraph( - paragraph, - Text { - content: &label, - ..option_text - }, - ); + paragraph.update(Text { + content: &label, + ..option_text + }); } if let Some(placeholder) = placeholder { - renderer.update_paragraph( - &mut state.placeholder, - Text { - content: placeholder, - ..option_text - }, - ); + state.placeholder.update(Text { + content: placeholder, + ..option_text + }); } let max_width = match width { diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 7d5ae806..f9a2d419 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -523,18 +523,15 @@ where shaping: text::Shaping::Advanced, }; - renderer.update_paragraph(&mut state.placeholder, placeholder_text); + state.placeholder.update(placeholder_text); let secure_value = is_secure.then(|| value.secure()); let value = secure_value.as_ref().unwrap_or(value); - renderer.update_paragraph( - &mut state.value, - Text { - content: &value.to_string(), - ..placeholder_text - }, - ); + state.value.update(Text { + content: &value.to_string(), + ..placeholder_text + }); if let Some(icon) = icon { let icon_text = Text { @@ -548,7 +545,7 @@ where shaping: text::Shaping::Advanced, }; - renderer.update_paragraph(&mut state.icon, icon_text); + state.icon.update(icon_text); let icon_width = state.icon.min_width(); @@ -1461,7 +1458,7 @@ fn replace_paragraph( let mut children_layout = layout.children(); let text_bounds = children_layout.next().unwrap().bounds(); - state.value = renderer.create_paragraph(Text { + state.value = Renderer::Paragraph::with_text(Text { font, line_height, content: &value.to_string(), -- cgit From 6448429103c9c82b90040ac5a5a097bdded23f82 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 12 Sep 2023 14:51:00 +0200 Subject: Draft `Editor` API and `TextEditor` widget --- core/src/layout/limits.rs | 2 +- core/src/lib.rs | 2 +- core/src/renderer/null.rs | 38 ++++ core/src/text.rs | 123 ++++-------- core/src/text/editor.rs | 68 +++++++ core/src/text/paragraph.rs | 59 ++++++ examples/editor/Cargo.toml | 10 + examples/editor/src/main.rs | 49 +++++ graphics/src/damage.rs | 7 + graphics/src/primitive.rs | 10 + graphics/src/renderer.rs | 14 ++ graphics/src/text.rs | 2 + graphics/src/text/editor.rs | 327 +++++++++++++++++++++++++++++++ renderer/src/lib.rs | 19 +- style/src/lib.rs | 1 + style/src/text_editor.rs | 47 +++++ style/src/theme.rs | 113 +++++++++++ tiny_skia/src/backend.rs | 25 +++ tiny_skia/src/text.rs | 32 ++++ wgpu/src/layer.rs | 15 +- wgpu/src/layer/text.rs | 8 +- wgpu/src/text.rs | 28 ++- widget/src/helpers.rs | 15 ++ widget/src/lib.rs | 5 +- widget/src/text_editor.rs | 457 ++++++++++++++++++++++++++++++++++++++++++++ 25 files changed, 1384 insertions(+), 92 deletions(-) create mode 100644 core/src/text/editor.rs create mode 100644 core/src/text/paragraph.rs create mode 100644 examples/editor/Cargo.toml create mode 100644 examples/editor/src/main.rs create mode 100644 graphics/src/text/editor.rs create mode 100644 style/src/text_editor.rs create mode 100644 widget/src/text_editor.rs diff --git a/core/src/layout/limits.rs b/core/src/layout/limits.rs index 5d3c1556..39a3d98b 100644 --- a/core/src/layout/limits.rs +++ b/core/src/layout/limits.rs @@ -2,7 +2,7 @@ use crate::{Length, Padding, Size}; /// A set of size constraints for layouting. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Limits { min: Size, max: Size, diff --git a/core/src/lib.rs b/core/src/lib.rs index 1bfba7bd..13a9f06b 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -12,7 +12,7 @@ #![forbid(unsafe_code, rust_2018_idioms)] #![deny( missing_debug_implementations, - missing_docs, + // missing_docs, unused_results, clippy::extra_unused_lifetimes, clippy::from_over_into, diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index 0ffd3649..adf75969 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -43,6 +43,7 @@ impl Renderer for Null { impl text::Renderer for Null { type Font = Font; type Paragraph = (); + type Editor = (); const ICON_FONT: Font = Font::DEFAULT; const CHECKMARK_ICON: char = '0'; @@ -66,6 +67,14 @@ impl text::Renderer for Null { ) { } + fn fill_editor( + &mut self, + _editor: &Self::Editor, + _position: Point, + _color: Color, + ) { + } + fn fill_text( &mut self, _paragraph: Text<'_, Self::Font>, @@ -106,3 +115,32 @@ impl text::Paragraph for () { None } } + +impl text::Editor for () { + type Font = Font; + + fn with_text(_text: &str) -> Self {} + + fn cursor(&self) -> text::editor::Cursor { + text::editor::Cursor::Caret(Point::ORIGIN) + } + + fn perform(&mut self, _action: text::editor::Action) {} + + fn bounds(&self) -> Size { + Size::ZERO + } + + fn min_bounds(&self) -> Size { + Size::ZERO + } + + fn update( + &mut self, + _new_bounds: Size, + _new_font: Self::Font, + _new_size: Pixels, + _new_line_height: text::LineHeight, + ) { + } +} diff --git a/core/src/text.rs b/core/src/text.rs index ff85696e..5aacbcc5 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -1,4 +1,11 @@ //! Draw and interact with text. +mod paragraph; + +pub mod editor; + +pub use editor::Editor; +pub use paragraph::Paragraph; + use crate::alignment; use crate::{Color, Pixels, Point, Size}; @@ -126,6 +133,31 @@ impl Hit { } } +/// The difference detected in some text. +/// +/// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some +/// [`Text`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Difference { + /// No difference. + /// + /// The text can be reused as it is! + None, + + /// A bounds difference. + /// + /// This normally means a relayout is necessary, but the shape of the text can + /// be reused. + Bounds, + + /// A shape difference. + /// + /// The contents, alignment, sizes, fonts, or any other essential attributes + /// of the shape of the text have changed. A complete reshape and relayout of + /// the text is necessary. + Shape, +} + /// A renderer capable of measuring and drawing [`Text`]. pub trait Renderer: crate::Renderer { /// The font type used. @@ -134,6 +166,9 @@ pub trait Renderer: crate::Renderer { /// The [`Paragraph`] of this [`Renderer`]. type Paragraph: Paragraph + 'static; + /// The [`Editor`] of this [`Renderer`]. + type Editor: Editor + 'static; + /// The icon font of the backend. const ICON_FONT: Self::Font; @@ -165,6 +200,13 @@ pub trait Renderer: crate::Renderer { color: Color, ); + fn fill_editor( + &mut self, + editor: &Self::Editor, + position: Point, + color: Color, + ); + /// Draws the given [`Text`] at the given position and with the given /// [`Color`]. fn fill_text( @@ -174,84 +216,3 @@ pub trait Renderer: crate::Renderer { color: Color, ); } - -/// A text paragraph. -pub trait Paragraph: Sized + Default { - /// The font of this [`Paragraph`]. - type Font: Copy + PartialEq; - - /// Creates a new [`Paragraph`] laid out with the given [`Text`]. - fn with_text(text: Text<'_, Self::Font>) -> Self; - - /// Lays out the [`Paragraph`] with some new boundaries. - fn resize(&mut self, new_bounds: Size); - - /// Compares the [`Paragraph`] with some desired [`Text`] and returns the - /// [`Difference`]. - fn compare(&self, text: Text<'_, Self::Font>) -> Difference; - - /// Returns the horizontal alignment of the [`Paragraph`]. - fn horizontal_alignment(&self) -> alignment::Horizontal; - - /// Returns the vertical alignment of the [`Paragraph`]. - fn vertical_alignment(&self) -> alignment::Vertical; - - /// Returns the minimum boundaries that can fit the contents of the - /// [`Paragraph`]. - fn min_bounds(&self) -> Size; - - /// Tests whether the provided point is within the boundaries of the - /// [`Paragraph`], returning information about the nearest character. - fn hit_test(&self, point: Point) -> Option; - - /// Returns the distance to the given grapheme index in the [`Paragraph`]. - fn grapheme_position(&self, line: usize, index: usize) -> Option; - - /// Updates the [`Paragraph`] to match the given [`Text`], if needed. - fn update(&mut self, text: Text<'_, Self::Font>) { - match self.compare(text) { - Difference::None => {} - Difference::Bounds => { - self.resize(text.bounds); - } - Difference::Shape => { - *self = Self::with_text(text); - } - } - } - - /// Returns the minimum width that can fit the contents of the [`Paragraph`]. - fn min_width(&self) -> f32 { - self.min_bounds().width - } - - /// Returns the minimum height that can fit the contents of the [`Paragraph`]. - fn min_height(&self) -> f32 { - self.min_bounds().height - } -} - -/// The difference detected in some text. -/// -/// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some -/// [`Text`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Difference { - /// No difference. - /// - /// The text can be reused as it is! - None, - - /// A bounds difference. - /// - /// This normally means a relayout is necessary, but the shape of the text can - /// be reused. - Bounds, - - /// A shape difference. - /// - /// The contents, alignment, sizes, fonts, or any other essential attributes - /// of the shape of the text have changed. A complete reshape and relayout of - /// the text is necessary. - Shape, -} diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs new file mode 100644 index 00000000..a4fd0ec1 --- /dev/null +++ b/core/src/text/editor.rs @@ -0,0 +1,68 @@ +use crate::text::LineHeight; +use crate::{Pixels, Point, Rectangle, Size}; + +pub trait Editor: Sized + Default { + type Font: Copy + PartialEq + Default; + + /// Creates a new [`Editor`] laid out with the given text. + fn with_text(text: &str) -> Self; + + fn cursor(&self) -> Cursor; + + fn perform(&mut self, action: Action); + + /// Returns the current boundaries of the [`Editor`]. + fn bounds(&self) -> Size; + + /// Returns the minimum boundaries that can fit the contents of the + /// [`Editor`]. + fn min_bounds(&self) -> Size; + + /// Updates the [`Editor`] with some new attributes. + fn update( + &mut self, + new_bounds: Size, + new_font: Self::Font, + new_size: Pixels, + new_line_height: LineHeight, + ); + + /// Returns the minimum width that can fit the contents of the [`Editor`]. + fn min_width(&self) -> f32 { + self.min_bounds().width + } + + /// Returns the minimum height that can fit the contents of the [`Editor`]. + fn min_height(&self) -> f32 { + self.min_bounds().height + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Action { + MoveLeft, + MoveRight, + MoveUp, + MoveDown, + MoveLeftWord, + MoveRightWord, + MoveHome, + MoveEnd, + SelectWord, + SelectLine, + Insert(char), + Backspace, + Delete, + Click(Point), + Drag(Point), +} + +/// The cursor of an [`Editor`]. +#[derive(Debug, Clone)] +pub enum Cursor { + /// Cursor without a selection + Caret(Point), + + /// Cursor selecting a range of text + Selection(Vec), +} diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs new file mode 100644 index 00000000..de1fb74d --- /dev/null +++ b/core/src/text/paragraph.rs @@ -0,0 +1,59 @@ +use crate::alignment; +use crate::text::{Difference, Hit, Text}; +use crate::{Point, Size}; + +/// A text paragraph. +pub trait Paragraph: Sized + Default { + /// The font of this [`Paragraph`]. + type Font: Copy + PartialEq; + + /// Creates a new [`Paragraph`] laid out with the given [`Text`]. + fn with_text(text: Text<'_, Self::Font>) -> Self; + + /// Lays out the [`Paragraph`] with some new boundaries. + fn resize(&mut self, new_bounds: Size); + + /// Compares the [`Paragraph`] with some desired [`Text`] and returns the + /// [`Difference`]. + fn compare(&self, text: Text<'_, Self::Font>) -> Difference; + + /// Returns the horizontal alignment of the [`Paragraph`]. + fn horizontal_alignment(&self) -> alignment::Horizontal; + + /// Returns the vertical alignment of the [`Paragraph`]. + fn vertical_alignment(&self) -> alignment::Vertical; + + /// Returns the minimum boundaries that can fit the contents of the + /// [`Paragraph`]. + fn min_bounds(&self) -> Size; + + /// Tests whether the provided point is within the boundaries of the + /// [`Paragraph`], returning information about the nearest character. + fn hit_test(&self, point: Point) -> Option; + + /// Returns the distance to the given grapheme index in the [`Paragraph`]. + fn grapheme_position(&self, line: usize, index: usize) -> Option; + + /// Updates the [`Paragraph`] to match the given [`Text`], if needed. + fn update(&mut self, text: Text<'_, Self::Font>) { + match self.compare(text) { + Difference::None => {} + Difference::Bounds => { + self.resize(text.bounds); + } + Difference::Shape => { + *self = Self::with_text(text); + } + } + } + + /// Returns the minimum width that can fit the contents of the [`Paragraph`]. + fn min_width(&self) -> f32 { + self.min_bounds().width + } + + /// Returns the minimum height that can fit the contents of the [`Paragraph`]. + fn min_height(&self) -> f32 { + self.min_bounds().height + } +} diff --git a/examples/editor/Cargo.toml b/examples/editor/Cargo.toml new file mode 100644 index 00000000..528cf23c --- /dev/null +++ b/examples/editor/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "editor" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2021" +publish = false + +[dependencies] +iced.workspace = true +iced.features = ["debug"] \ No newline at end of file diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs new file mode 100644 index 00000000..50989ac5 --- /dev/null +++ b/examples/editor/src/main.rs @@ -0,0 +1,49 @@ +use iced::widget::{container, text_editor}; +use iced::{Element, Font, Sandbox, Settings}; + +pub fn main() -> iced::Result { + Editor::run(Settings::default()) +} + +struct Editor { + content: text_editor::Content, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + Edit(text_editor::Action), +} + +impl Sandbox for Editor { + type Message = Message; + + fn new() -> Self { + Self { + content: text_editor::Content::with(include_str!( + "../../../README.md" + )), + } + } + + fn title(&self) -> String { + String::from("Editor - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::Edit(action) => { + self.content.edit(action); + } + } + } + + fn view(&self) -> Element { + container( + text_editor(&self.content) + .on_edit(Message::Edit) + .font(Font::with_name("Hasklug Nerd Font Mono")), + ) + .padding(20) + .into() + } +} diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs index 3276c2d4..595cc274 100644 --- a/graphics/src/damage.rs +++ b/graphics/src/damage.rs @@ -66,6 +66,13 @@ impl Damage for Primitive { bounds.expand(1.5) } + Self::Editor { + editor, position, .. + } => { + let bounds = Rectangle::new(*position, editor.bounds); + + bounds.expand(1.5) + } Self::Quad { bounds, .. } | Self::Image { bounds, .. } | Self::Svg { bounds, .. } => bounds.expand(1.0), diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 8a97e6e7..ce0b734b 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -4,6 +4,7 @@ use crate::core::image; use crate::core::svg; use crate::core::text; use crate::core::{Background, Color, Font, Pixels, Point, Rectangle, Vector}; +use crate::text::editor; use crate::text::paragraph; use std::sync::Arc; @@ -41,6 +42,15 @@ pub enum Primitive { /// The color of the paragraph. color: Color, }, + /// An editor primitive + Editor { + /// The [`editor::Weak`] reference. + editor: editor::Weak, + /// The position of the paragraph. + position: Point, + /// The color of the paragraph. + color: Color, + }, /// A quad primitive Quad { /// The bounds of the quad diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index c5033d36..9b699183 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -141,6 +141,7 @@ where { type Font = Font; type Paragraph = text::Paragraph; + type Editor = text::Editor; const ICON_FONT: Font = Font::with_name("Iced-Icons"); const CHECKMARK_ICON: char = '\u{f00c}'; @@ -171,6 +172,19 @@ where }); } + fn fill_editor( + &mut self, + editor: &Self::Editor, + position: Point, + color: Color, + ) { + self.primitives.push(Primitive::Editor { + editor: editor.downgrade(), + position, + color, + }); + } + fn fill_text( &mut self, text: Text<'_, Self::Font>, diff --git a/graphics/src/text.rs b/graphics/src/text.rs index f5ccaf52..280e4f01 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -1,7 +1,9 @@ pub mod cache; +pub mod editor; pub mod paragraph; pub use cache::Cache; +pub use editor::Editor; pub use paragraph::Paragraph; pub use cosmic_text; diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs new file mode 100644 index 00000000..53f63fea --- /dev/null +++ b/graphics/src/text/editor.rs @@ -0,0 +1,327 @@ +use crate::core::text::editor::{self, Action, Cursor}; +use crate::core::text::LineHeight; +use crate::core::{Font, Pixels, Point, Size}; +use crate::text; + +use cosmic_text::Edit; + +use std::fmt; +use std::sync::{self, Arc}; + +#[derive(Debug, PartialEq)] +pub struct Editor(Option>); + +struct Internal { + editor: cosmic_text::Editor, + font: Font, + bounds: Size, + min_bounds: Size, + version: text::Version, +} + +impl Editor { + pub fn new() -> Self { + Self::default() + } + + pub fn buffer(&self) -> &cosmic_text::Buffer { + &self.internal().editor.buffer() + } + + pub fn downgrade(&self) -> Weak { + let editor = self.internal(); + + Weak { + raw: Arc::downgrade(editor), + bounds: editor.bounds, + } + } + + fn internal(&self) -> &Arc { + self.0 + .as_ref() + .expect("editor should always be initialized") + } +} + +impl editor::Editor for Editor { + type Font = Font; + + fn with_text(text: &str) -> Self { + let mut buffer = cosmic_text::Buffer::new_empty(cosmic_text::Metrics { + font_size: 1.0, + line_height: 1.0, + }); + + buffer.set_text( + text::font_system() + .write() + .expect("Write font system") + .raw(), + text, + cosmic_text::Attrs::new(), + cosmic_text::Shaping::Advanced, + ); + + Editor(Some(Arc::new(Internal { + editor: cosmic_text::Editor::new(buffer), + ..Default::default() + }))) + } + + fn cursor(&self) -> editor::Cursor { + let internal = self.internal(); + + match internal.editor.select_opt() { + Some(selection) => { + // TODO + Cursor::Selection(vec![]) + } + None => { + let cursor = internal.editor.cursor(); + let buffer = internal.editor.buffer(); + + let lines_before_cursor: usize = buffer + .lines + .iter() + .take(cursor.line) + .map(|line| { + line.layout_opt() + .as_ref() + .expect("Line layout should be cached") + .len() + }) + .sum(); + + let line = buffer + .lines + .get(cursor.line) + .expect("Cursor line should be present"); + + let layout = line + .layout_opt() + .as_ref() + .expect("Line layout should be cached"); + + let mut lines = layout.iter().enumerate(); + + let (subline, offset) = lines + .find_map(|(i, line)| { + let start = line + .glyphs + .first() + .map(|glyph| glyph.start) + .unwrap_or(0); + let end = line + .glyphs + .last() + .map(|glyph| glyph.end) + .unwrap_or(0); + + let is_cursor_after_start = start <= cursor.index; + + let is_cursor_before_end = match cursor.affinity { + cosmic_text::Affinity::Before => { + cursor.index <= end + } + cosmic_text::Affinity::After => cursor.index < end, + }; + + if is_cursor_after_start && is_cursor_before_end { + let offset = line + .glyphs + .iter() + .take_while(|glyph| cursor.index > glyph.start) + .map(|glyph| glyph.w) + .sum(); + + Some((i, offset)) + } else { + None + } + }) + .unwrap_or((0, 0.0)); + + let line_height = buffer.metrics().line_height; + + let scroll_offset = buffer.scroll() as f32 * line_height; + + Cursor::Caret(Point::new( + offset, + (lines_before_cursor + subline) as f32 * line_height + - scroll_offset, + )) + } + } + } + + fn perform(&mut self, action: Action) { + let mut font_system = + text::font_system().write().expect("Write font system"); + + let editor = + self.0.take().expect("Editor should always be initialized"); + + // TODO: Handle multiple strong references somehow + let mut internal = Arc::try_unwrap(editor) + .expect("Editor cannot have multiple strong references"); + + let editor = &mut internal.editor; + + let mut act = |action| editor.action(font_system.raw(), action); + + match action { + Action::MoveLeft => act(cosmic_text::Action::Left), + Action::MoveRight => act(cosmic_text::Action::Right), + Action::MoveUp => act(cosmic_text::Action::Up), + Action::MoveDown => act(cosmic_text::Action::Down), + Action::Insert(c) => act(cosmic_text::Action::Insert(c)), + Action::Backspace => act(cosmic_text::Action::Backspace), + Action::Delete => act(cosmic_text::Action::Delete), + Action::Click(position) => act(cosmic_text::Action::Click { + x: position.x as i32, + y: position.y as i32, + }), + Action::Drag(position) => act(cosmic_text::Action::Drag { + x: position.x as i32, + y: position.y as i32, + }), + _ => todo!(), + } + + editor.shape_as_needed(font_system.raw()); + + self.0 = Some(Arc::new(internal)); + } + + fn bounds(&self) -> Size { + self.internal().bounds + } + + fn min_bounds(&self) -> Size { + self.internal().min_bounds + } + + fn update( + &mut self, + new_bounds: Size, + new_font: Font, + new_size: Pixels, + new_line_height: LineHeight, + ) { + let editor = + self.0.take().expect("editor should always be initialized"); + + let mut internal = Arc::try_unwrap(editor) + .expect("Editor cannot have multiple strong references"); + + let mut font_system = + text::font_system().write().expect("Write font system"); + + let mut changed = false; + + if new_font != internal.font { + for line in internal.editor.buffer_mut().lines.iter_mut() { + let _ = line.set_attrs_list(cosmic_text::AttrsList::new( + text::to_attributes(new_font), + )); + } + + changed = true; + } + + let metrics = internal.editor.buffer().metrics(); + let new_line_height = new_line_height.to_absolute(new_size); + + if new_size.0 != metrics.font_size + || new_line_height.0 != metrics.line_height + { + internal.editor.buffer_mut().set_metrics( + font_system.raw(), + cosmic_text::Metrics::new(new_size.0, new_line_height.0), + ); + + changed = true; + } + + if new_bounds != internal.bounds { + internal.editor.buffer_mut().set_size( + font_system.raw(), + new_bounds.width, + new_bounds.height, + ); + + internal.bounds = new_bounds; + changed = true; + } + + if changed { + internal.min_bounds = text::measure(&internal.editor.buffer()); + } + + self.0 = Some(Arc::new(internal)); + } +} + +impl Default for Editor { + fn default() -> Self { + Self(Some(Arc::new(Internal::default()))) + } +} + +impl PartialEq for Internal { + fn eq(&self, other: &Self) -> bool { + self.font == other.font + && self.bounds == other.bounds + && self.min_bounds == other.min_bounds + && self.editor.buffer().metrics() == other.editor.buffer().metrics() + } +} + +impl Default for Internal { + fn default() -> Self { + Self { + editor: cosmic_text::Editor::new(cosmic_text::Buffer::new_empty( + cosmic_text::Metrics { + font_size: 1.0, + line_height: 1.0, + }, + )), + font: Font::default(), + bounds: Size::ZERO, + min_bounds: Size::ZERO, + version: text::Version::default(), + } + } +} + +impl fmt::Debug for Internal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Internal") + .field("font", &self.font) + .field("bounds", &self.bounds) + .field("min_bounds", &self.min_bounds) + .finish() + } +} + +#[derive(Debug, Clone)] +pub struct Weak { + raw: sync::Weak, + pub bounds: Size, +} + +impl Weak { + pub fn upgrade(&self) -> Option { + self.raw.upgrade().map(Some).map(Editor) + } +} + +impl PartialEq for Weak { + fn eq(&self, other: &Self) -> bool { + match (self.raw.upgrade(), other.raw.upgrade()) { + (Some(p1), Some(p2)) => p1 == p2, + _ => false, + } + } +} diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 73e56890..6f044af6 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -32,6 +32,7 @@ use crate::core::text::{self, Text}; use crate::core::{ Background, Color, Font, Pixels, Point, Rectangle, Size, Vector, }; +use crate::graphics::text::Editor; use crate::graphics::text::Paragraph; use crate::graphics::Mesh; @@ -159,6 +160,7 @@ impl core::Renderer for Renderer { impl text::Renderer for Renderer { type Font = Font; type Paragraph = Paragraph; + type Editor = Editor; const ICON_FONT: Font = iced_tiny_skia::Renderer::::ICON_FONT; const CHECKMARK_ICON: char = iced_tiny_skia::Renderer::::CHECKMARK_ICON; @@ -179,14 +181,27 @@ impl text::Renderer for Renderer { fn fill_paragraph( &mut self, - text: &Self::Paragraph, + paragraph: &Self::Paragraph, position: Point, color: Color, ) { delegate!( self, renderer, - renderer.fill_paragraph(text, position, color) + renderer.fill_paragraph(paragraph, position, color) + ); + } + + fn fill_editor( + &mut self, + editor: &Self::Editor, + position: Point, + color: Color, + ) { + delegate!( + self, + renderer, + renderer.fill_editor(editor, position, color) ); } diff --git a/style/src/lib.rs b/style/src/lib.rs index 0c555ed8..7a97ac77 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -35,6 +35,7 @@ pub mod rule; pub mod scrollable; pub mod slider; pub mod svg; +pub mod text_editor; pub mod text_input; pub mod theme; pub mod toggler; diff --git a/style/src/text_editor.rs b/style/src/text_editor.rs new file mode 100644 index 00000000..45c9bad8 --- /dev/null +++ b/style/src/text_editor.rs @@ -0,0 +1,47 @@ +//! Change the appearance of a text editor. +use iced_core::{Background, BorderRadius, Color}; + +/// The appearance of a text input. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The [`Background`] of the text input. + pub background: Background, + /// The border radius of the text input. + pub border_radius: BorderRadius, + /// The border width of the text input. + pub border_width: f32, + /// The border [`Color`] of the text input. + pub border_color: Color, +} + +/// A set of rules that dictate the style of a text input. +pub trait StyleSheet { + /// The supported style of the [`StyleSheet`]. + type Style: Default; + + /// Produces the style of an active text input. + fn active(&self, style: &Self::Style) -> Appearance; + + /// Produces the style of a focused text input. + fn focused(&self, style: &Self::Style) -> Appearance; + + /// Produces the [`Color`] of the placeholder of a text input. + fn placeholder_color(&self, style: &Self::Style) -> Color; + + /// Produces the [`Color`] of the value of a text input. + fn value_color(&self, style: &Self::Style) -> Color; + + /// Produces the [`Color`] of the value of a disabled text input. + fn disabled_color(&self, style: &Self::Style) -> Color; + + /// Produces the [`Color`] of the selection of a text input. + fn selection_color(&self, style: &Self::Style) -> Color; + + /// Produces the style of an hovered text input. + fn hovered(&self, style: &Self::Style) -> Appearance { + self.focused(style) + } + + /// Produces the style of a disabled text input. + fn disabled(&self, style: &Self::Style) -> Appearance; +} diff --git a/style/src/theme.rs b/style/src/theme.rs index 893d7202..a1501c01 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -17,6 +17,7 @@ use crate::rule; use crate::scrollable; use crate::slider; use crate::svg; +use crate::text_editor; use crate::text_input; use crate::toggler; @@ -1174,3 +1175,115 @@ impl text_input::StyleSheet for Theme { self.placeholder_color(style) } } + +/// The style of a text input. +#[derive(Default)] +pub enum TextEditor { + /// The default style. + #[default] + Default, + /// A custom style. + Custom(Box>), +} + +impl text_editor::StyleSheet for Theme { + type Style = TextEditor; + + fn active(&self, style: &Self::Style) -> text_editor::Appearance { + if let TextEditor::Custom(custom) = style { + return custom.active(self); + } + + let palette = self.extended_palette(); + + text_editor::Appearance { + background: palette.background.base.color.into(), + border_radius: 2.0.into(), + border_width: 1.0, + border_color: palette.background.strong.color, + } + } + + fn hovered(&self, style: &Self::Style) -> text_editor::Appearance { + if let TextEditor::Custom(custom) = style { + return custom.hovered(self); + } + + let palette = self.extended_palette(); + + text_editor::Appearance { + background: palette.background.base.color.into(), + border_radius: 2.0.into(), + border_width: 1.0, + border_color: palette.background.base.text, + } + } + + fn focused(&self, style: &Self::Style) -> text_editor::Appearance { + if let TextEditor::Custom(custom) = style { + return custom.focused(self); + } + + let palette = self.extended_palette(); + + text_editor::Appearance { + background: palette.background.base.color.into(), + border_radius: 2.0.into(), + border_width: 1.0, + border_color: palette.primary.strong.color, + } + } + + fn placeholder_color(&self, style: &Self::Style) -> Color { + if let TextEditor::Custom(custom) = style { + return custom.placeholder_color(self); + } + + let palette = self.extended_palette(); + + palette.background.strong.color + } + + fn value_color(&self, style: &Self::Style) -> Color { + if let TextEditor::Custom(custom) = style { + return custom.value_color(self); + } + + let palette = self.extended_palette(); + + palette.background.base.text + } + + fn selection_color(&self, style: &Self::Style) -> Color { + if let TextEditor::Custom(custom) = style { + return custom.selection_color(self); + } + + let palette = self.extended_palette(); + + palette.primary.weak.color + } + + fn disabled(&self, style: &Self::Style) -> text_editor::Appearance { + if let TextEditor::Custom(custom) = style { + return custom.disabled(self); + } + + let palette = self.extended_palette(); + + text_editor::Appearance { + background: palette.background.weak.color.into(), + border_radius: 2.0.into(), + border_width: 1.0, + border_color: palette.background.strong.color, + } + } + + fn disabled_color(&self, style: &Self::Style) -> Color { + if let TextEditor::Custom(custom) = style { + return custom.disabled_color(self); + } + + self.placeholder_color(style) + } +} diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 72184c8a..5f66dff2 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -383,6 +383,31 @@ impl Backend { clip_mask, ); } + Primitive::Editor { + editor, + position, + color, + } => { + let physical_bounds = + (Rectangle::new(*position, editor.bounds) + translation) + * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + self.text_pipeline.draw_editor( + editor, + *position + translation, + *color, + scale_factor, + pixels, + clip_mask, + ); + } Primitive::Text { content, bounds, diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 4f6e3941..d055c749 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -2,6 +2,7 @@ use crate::core::alignment; use crate::core::text::{LineHeight, Shaping}; use crate::core::{Color, Font, Pixels, Point, Rectangle}; use crate::graphics::text::cache::{self, Cache}; +use crate::graphics::text::editor; use crate::graphics::text::font_system; use crate::graphics::text::paragraph; @@ -64,6 +65,37 @@ impl Pipeline { ); } + pub fn draw_editor( + &mut self, + editor: &editor::Weak, + position: Point, + color: Color, + scale_factor: f32, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::Mask>, + ) { + use crate::core::text::Editor as _; + + let Some(editor) = editor.upgrade() else { + return; + }; + + let mut font_system = font_system().write().expect("Write font system"); + + draw( + font_system.raw(), + &mut self.glyph_cache, + editor.buffer(), + Rectangle::new(position, editor.min_bounds()), + color, + alignment::Horizontal::Left, + alignment::Vertical::Top, + scale_factor, + pixels, + clip_mask, + ); + } + pub fn draw_cached( &mut self, content: &str, diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 7a5a0f7c..10b3332d 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -120,12 +120,25 @@ impl<'a> Layer<'a> { } => { let layer = &mut layers[current_layer]; - layer.text.push(Text::Managed { + layer.text.push(Text::Paragraph { paragraph: paragraph.clone(), position: *position + translation, color: *color, }); } + Primitive::Editor { + editor, + position, + color, + } => { + let layer = &mut layers[current_layer]; + + layer.text.push(Text::Editor { + editor: editor.clone(), + position: *position + translation, + color: *color, + }); + } Primitive::Text { content, bounds, diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs index b61615d6..d46b39da 100644 --- a/wgpu/src/layer/text.rs +++ b/wgpu/src/layer/text.rs @@ -1,16 +1,22 @@ use crate::core::alignment; use crate::core::text; use crate::core::{Color, Font, Pixels, Point, Rectangle}; +use crate::graphics::text::editor; use crate::graphics::text::paragraph; /// A paragraph of text. #[derive(Debug, Clone)] pub enum Text<'a> { - Managed { + Paragraph { paragraph: paragraph::Weak, position: Point, color: Color, }, + Editor { + editor: editor::Weak, + position: Point, + color: Color, + }, Cached(Cached<'a>), } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 5c9f4d7e..397c38dd 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -2,7 +2,7 @@ use crate::core::alignment; use crate::core::{Rectangle, Size}; use crate::graphics::color; use crate::graphics::text::cache::{self, Cache}; -use crate::graphics::text::{font_system, Paragraph}; +use crate::graphics::text::{font_system, Editor, Paragraph}; use crate::layer::Text; use std::borrow::Cow; @@ -74,15 +74,19 @@ impl Pipeline { enum Allocation { Paragraph(Paragraph), + Editor(Editor), Cache(cache::KeyHash), } let allocations: Vec<_> = sections .iter() .map(|section| match section { - Text::Managed { paragraph, .. } => { + Text::Paragraph { paragraph, .. } => { paragraph.upgrade().map(Allocation::Paragraph) } + Text::Editor { editor, .. } => { + editor.upgrade().map(Allocation::Editor) + } Text::Cached(text) => { let (key, _) = cache.allocate( font_system, @@ -117,7 +121,7 @@ impl Pipeline { vertical_alignment, color, ) = match section { - Text::Managed { + Text::Paragraph { position, color, .. } => { use crate::core::text::Paragraph as _; @@ -135,6 +139,24 @@ impl Pipeline { *color, ) } + Text::Editor { + position, color, .. + } => { + use crate::core::text::Editor as _; + + let Some(Allocation::Editor(editor)) = allocation + else { + return None; + }; + + ( + editor.buffer(), + Rectangle::new(*position, editor.min_bounds()), + alignment::Horizontal::Left, + alignment::Vertical::Top, + *color, + ) + } Text::Cached(text) => { let Some(Allocation::Cache(key)) = allocation else { return None; diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 3c9c2b29..61541eac 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -16,6 +16,7 @@ use crate::runtime::Command; use crate::scrollable::{self, Scrollable}; use crate::slider::{self, Slider}; use crate::text::{self, Text}; +use crate::text_editor::{self, TextEditor}; use crate::text_input::{self, TextInput}; use crate::toggler::{self, Toggler}; use crate::tooltip::{self, Tooltip}; @@ -206,6 +207,20 @@ where TextInput::new(placeholder, value) } +/// Creates a new [`TextEditor`]. +/// +/// [`TextEditor`]: crate::TextEditor +pub fn text_editor<'a, Message, Renderer>( + content: &'a text_editor::Content, +) -> TextEditor<'a, Message, Renderer> +where + Message: Clone, + Renderer: core::text::Renderer, + Renderer::Theme: text_editor::StyleSheet, +{ + TextEditor::new(content) +} + /// Creates a new [`Slider`]. /// /// [`Slider`]: crate::Slider diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 7e204171..f8e5e865 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -4,8 +4,8 @@ )] #![forbid(unsafe_code, rust_2018_idioms)] #![deny( - missing_debug_implementations, - missing_docs, + // missing_debug_implementations, + // missing_docs, unused_results, clippy::extra_unused_lifetimes, clippy::from_over_into, @@ -41,6 +41,7 @@ pub mod scrollable; pub mod slider; pub mod space; pub mod text; +pub mod text_editor; pub mod text_input; pub mod toggler; pub mod tooltip; diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs new file mode 100644 index 00000000..d09f2c3e --- /dev/null +++ b/widget/src/text_editor.rs @@ -0,0 +1,457 @@ +use crate::core::event::{self, Event}; +use crate::core::keyboard; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::text::editor::{Cursor, Editor as _}; +use crate::core::text::{self, LineHeight}; +use crate::core::widget::{self, Widget}; +use crate::core::{ + Clipboard, Color, Element, Length, Padding, Pixels, Point, Rectangle, + Shell, Vector, +}; + +use std::cell::RefCell; + +pub use crate::style::text_editor::{Appearance, StyleSheet}; +pub use text::editor::Action; + +pub struct TextEditor<'a, Message, Renderer = crate::Renderer> +where + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + content: &'a Content, + font: Option, + text_size: Option, + line_height: LineHeight, + width: Length, + height: Length, + padding: Padding, + style: ::Style, + on_edit: Option Message + 'a>>, +} + +impl<'a, Message, Renderer> TextEditor<'a, Message, Renderer> +where + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + pub fn new(content: &'a Content) -> Self { + Self { + content, + font: None, + text_size: None, + line_height: LineHeight::default(), + width: Length::Fill, + height: Length::Fill, + padding: Padding::new(5.0), + style: Default::default(), + on_edit: None, + } + } + + pub fn on_edit(mut self, on_edit: impl Fn(Action) -> Message + 'a) -> Self { + self.on_edit = Some(Box::new(on_edit)); + self + } + + pub fn font(mut self, font: impl Into) -> Self { + self.font = Some(font.into()); + self + } + + pub fn padding(mut self, padding: impl Into) -> Self { + self.padding = padding.into(); + self + } +} + +pub struct Content(RefCell>) +where + R: text::Renderer; + +struct Internal +where + R: text::Renderer, +{ + editor: R::Editor, + is_dirty: bool, +} + +impl Content +where + R: text::Renderer, +{ + pub fn new() -> Self { + Self::with("") + } + + pub fn with(text: &str) -> Self { + Self(RefCell::new(Internal { + editor: R::Editor::with_text(text), + is_dirty: true, + })) + } + + pub fn edit(&mut self, action: Action) { + let internal = self.0.get_mut(); + + internal.editor.perform(action); + internal.is_dirty = true; + } +} + +impl Default for Content +where + Renderer: text::Renderer, +{ + fn default() -> Self { + Self::new() + } +} + +struct State { + is_focused: bool, + is_dragging: bool, + last_click: Option, +} + +impl<'a, Message, Renderer> Widget + for TextEditor<'a, Message, Renderer> +where + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + fn tag(&self) -> widget::tree::Tag { + widget::tree::Tag::of::() + } + + fn state(&self) -> widget::tree::State { + widget::tree::State::new(State { + is_focused: false, + is_dragging: false, + last_click: None, + }) + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + _tree: &mut widget::Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> iced_renderer::core::layout::Node { + let mut internal = self.content.0.borrow_mut(); + + internal.editor.update( + limits.pad(self.padding).max(), + self.font.unwrap_or_else(|| renderer.default_font()), + self.text_size.unwrap_or_else(|| renderer.default_size()), + self.line_height, + ); + + layout::Node::new(limits.max()) + } + + 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>, + _viewport: &Rectangle, + ) -> event::Status { + let Some(on_edit) = self.on_edit.as_ref() else { + return event::Status::Ignored; + }; + + let state = tree.state.downcast_mut::(); + + let Some(update) = Update::from_event( + event, + state, + layout.bounds(), + self.padding, + cursor, + ) else { + return event::Status::Ignored; + }; + + match update { + Update::Focus { click, action } => { + state.is_focused = true; + state.last_click = Some(click); + shell.publish(on_edit(action)); + } + Update::Unfocus => { + state.is_focused = false; + state.is_dragging = false; + } + Update::Click { click, action } => { + state.last_click = Some(click); + state.is_dragging = true; + shell.publish(on_edit(action)); + } + Update::StopDragging => { + state.is_dragging = false; + } + Update::Edit(action) => { + shell.publish(on_edit(action)); + } + Update::Copy => {} + Update::Paste => if let Some(_contents) = clipboard.read() {}, + } + + event::Status::Captured + } + + fn draw( + &self, + tree: &widget::Tree, + renderer: &mut Renderer, + theme: &::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + + let internal = self.content.0.borrow(); + let state = tree.state.downcast_ref::(); + + let is_disabled = self.on_edit.is_none(); + let is_mouse_over = cursor.is_over(bounds); + + let appearance = if is_disabled { + theme.disabled(&self.style) + } else if state.is_focused { + theme.focused(&self.style) + } else if is_mouse_over { + theme.hovered(&self.style) + } else { + theme.active(&self.style) + }; + + renderer.fill_quad( + renderer::Quad { + bounds, + border_radius: appearance.border_radius, + border_width: appearance.border_width, + border_color: appearance.border_color, + }, + appearance.background, + ); + + renderer.fill_editor( + &internal.editor, + bounds.position() + + Vector::new(self.padding.left, self.padding.top), + style.text_color, + ); + + if state.is_focused { + match internal.editor.cursor() { + Cursor::Caret(position) => { + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: position.x + bounds.x + self.padding.left, + y: position.y + bounds.y + self.padding.top, + width: 1.0, + height: self + .line_height + .to_absolute(self.text_size.unwrap_or_else( + || renderer.default_size(), + )) + .into(), + }, + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + theme.value_color(&self.style), + ); + } + Cursor::Selection(ranges) => { + for range in ranges { + renderer.fill_quad( + renderer::Quad { + bounds: range + Vector::new(bounds.x, bounds.y), + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + theme.selection_color(&self.style), + ); + } + } + } + } + } + + fn mouse_interaction( + &self, + _state: &widget::Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + let is_disabled = self.on_edit.is_none(); + + if cursor.is_over(layout.bounds()) { + if is_disabled { + mouse::Interaction::NotAllowed + } else { + mouse::Interaction::Text + } + } else { + mouse::Interaction::default() + } + } +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Message: 'a, + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + fn from(text_editor: TextEditor<'a, Message, Renderer>) -> Self { + Self::new(text_editor) + } +} + +enum Update { + Focus { click: mouse::Click, action: Action }, + Unfocus, + Click { click: mouse::Click, action: Action }, + StopDragging, + Edit(Action), + Copy, + Paste, +} + +impl Update { + fn from_event( + event: Event, + state: &State, + bounds: Rectangle, + padding: Padding, + cursor: mouse::Cursor, + ) -> Option { + match event { + Event::Mouse(event) => match event { + mouse::Event::ButtonPressed(mouse::Button::Left) => { + if let Some(cursor_position) = cursor.position_in(bounds) { + let cursor_position = cursor_position + - Vector::new(padding.top, padding.left); + + if state.is_focused { + let click = mouse::Click::new( + cursor_position, + state.last_click, + ); + + let action = match click.kind() { + mouse::click::Kind::Single => { + Action::Click(cursor_position) + } + mouse::click::Kind::Double => { + Action::SelectWord + } + mouse::click::Kind::Triple => { + Action::SelectLine + } + }; + + Some(Update::Click { click, action }) + } else { + Some(Update::Focus { + click: mouse::Click::new(cursor_position, None), + action: Action::Click(cursor_position), + }) + } + } else if state.is_focused { + Some(Update::Unfocus) + } else { + None + } + } + mouse::Event::ButtonReleased(mouse::Button::Left) => { + Some(Update::StopDragging) + } + mouse::Event::CursorMoved { .. } if state.is_dragging => { + let cursor_position = cursor.position_in(bounds)? + - Vector::new(padding.top, padding.left); + + Some(Self::Edit(Action::Drag(cursor_position))) + } + _ => None, + }, + Event::Keyboard(event) => match event { + keyboard::Event::KeyPressed { + key_code, + modifiers, + } if state.is_focused => match key_code { + keyboard::KeyCode::Left => { + if platform::is_jump_modifier_pressed(modifiers) { + Some(Self::Edit(Action::MoveLeftWord)) + } else { + Some(Self::Edit(Action::MoveLeft)) + } + } + keyboard::KeyCode::Right => { + if platform::is_jump_modifier_pressed(modifiers) { + Some(Self::Edit(Action::MoveRightWord)) + } else { + Some(Self::Edit(Action::MoveRight)) + } + } + keyboard::KeyCode::Up => Some(Self::Edit(Action::MoveUp)), + keyboard::KeyCode::Down => { + Some(Self::Edit(Action::MoveDown)) + } + keyboard::KeyCode::Backspace => { + Some(Self::Edit(Action::Backspace)) + } + keyboard::KeyCode::Delete => { + Some(Self::Edit(Action::Delete)) + } + keyboard::KeyCode::Escape => Some(Self::Unfocus), + _ => None, + }, + keyboard::Event::CharacterReceived(c) if state.is_focused => { + Some(Self::Edit(Action::Insert(c))) + } + _ => None, + }, + _ => None, + } + } +} + +mod platform { + use crate::core::keyboard; + + pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool { + if cfg!(target_os = "macos") { + modifiers.alt() + } else { + modifiers.control() + } + } +} -- cgit From 1455911b636f19810e12eeb12a6eed11c5244cfe Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 12 Sep 2023 15:03:23 +0200 Subject: Add `Enter` variant to `Action` in `text::Editor` --- core/src/text/editor.rs | 1 + graphics/src/text/editor.rs | 1 + widget/src/text_editor.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs index a4fd0ec1..09d4efde 100644 --- a/core/src/text/editor.rs +++ b/core/src/text/editor.rs @@ -51,6 +51,7 @@ pub enum Action { SelectWord, SelectLine, Insert(char), + Enter, Backspace, Delete, Click(Point), diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 53f63fea..b4d6819f 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -176,6 +176,7 @@ impl editor::Editor for Editor { Action::MoveUp => act(cosmic_text::Action::Up), Action::MoveDown => act(cosmic_text::Action::Down), Action::Insert(c) => act(cosmic_text::Action::Insert(c)), + Action::Enter => act(cosmic_text::Action::Enter), Action::Backspace => act(cosmic_text::Action::Backspace), Action::Delete => act(cosmic_text::Action::Delete), Action::Click(position) => act(cosmic_text::Action::Click { diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index d09f2c3e..fcbd3dad 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -425,6 +425,7 @@ impl Update { keyboard::KeyCode::Down => { Some(Self::Edit(Action::MoveDown)) } + keyboard::KeyCode::Enter => Some(Self::Edit(Action::Enter)), keyboard::KeyCode::Backspace => { Some(Self::Edit(Action::Backspace)) } -- cgit From abab1448576fbfa4717b65cdf1455debf44f2df5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 12 Sep 2023 18:20:02 +0200 Subject: Return `Cursor::Caret` if selection matches cursor position in `Editor::cursor` --- graphics/src/text/editor.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index b4d6819f..7b0ddec1 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -72,15 +72,18 @@ impl editor::Editor for Editor { fn cursor(&self) -> editor::Cursor { let internal = self.internal(); + let cursor = internal.editor.cursor(); + let buffer = internal.editor.buffer(); + match internal.editor.select_opt() { - Some(selection) => { + Some(selection) + if cursor.line != selection.line + || cursor.index != selection.index => + { // TODO Cursor::Selection(vec![]) } - None => { - let cursor = internal.editor.cursor(); - let buffer = internal.editor.buffer(); - + _ => { let lines_before_cursor: usize = buffer .lines .iter() -- cgit From 4389ab9865d13e17ce3c66223d7c149437be692b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 12 Sep 2023 18:27:30 +0200 Subject: Fix cursor offset with `Affinity::After` at the end of lines in `Editor::cursor` --- graphics/src/text/editor.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 7b0ddec1..b39e9831 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -143,7 +143,10 @@ impl editor::Editor for Editor { None } }) - .unwrap_or((0, 0.0)); + .unwrap_or(( + 0, + layout.last().map(|line| line.w).unwrap_or(0.0), + )); let line_height = buffer.metrics().line_height; -- cgit From a28ed825c1f48c61a655c5583eb207999e98f400 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 12 Sep 2023 20:57:46 +0200 Subject: Fix subline positioning in `Editor::cursor` --- graphics/src/text/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index b39e9831..52a5d942 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -144,7 +144,7 @@ impl editor::Editor for Editor { } }) .unwrap_or(( - 0, + layout.len().saturating_sub(1), layout.last().map(|line| line.w).unwrap_or(0.0), )); -- cgit From 40eb648f1e1e2ceb2782eddacbbc966f44de6961 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 13 Sep 2023 15:00:33 +0200 Subject: Implement `Cursor::Selection` calculation in `Editor::cursor` --- graphics/src/text/editor.rs | 118 ++++++++++++++++++++++++++++++++++++++++++-- widget/src/text_editor.rs | 6 ++- 2 files changed, 120 insertions(+), 4 deletions(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 52a5d942..3544bde6 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -1,6 +1,6 @@ use crate::core::text::editor::{self, Action, Cursor}; use crate::core::text::LineHeight; -use crate::core::{Font, Pixels, Point, Size}; +use crate::core::{Font, Pixels, Point, Rectangle, Size, Vector}; use crate::text; use cosmic_text::Edit; @@ -80,8 +80,70 @@ impl editor::Editor for Editor { if cursor.line != selection.line || cursor.index != selection.index => { - // TODO - Cursor::Selection(vec![]) + let line_height = buffer.metrics().line_height; + let scroll_offset = buffer.scroll() as f32 * line_height; + + let (start, end) = if cursor < selection { + (cursor, selection) + } else { + (selection, cursor) + }; + + let visual_lines_before_start: usize = buffer + .lines + .iter() + .take(start.line) + .map(|line| { + line.layout_opt() + .as_ref() + .expect("Line layout should be cached") + .len() + }) + .sum(); + + let selected_lines = end.line - start.line + 1; + + let regions = buffer + .lines + .iter() + .skip(start.line) + .take(selected_lines) + .enumerate() + .flat_map(|(i, line)| { + highlight_line( + line, + if i == 0 { start.index } else { 0 }, + if i == selected_lines - 1 { + end.index + } else { + line.text().len() + }, + ) + }) + .enumerate() + .filter_map(|(visual_line, (x, width))| { + if width > 0.0 { + Some(Rectangle { + x, + width, + y: visual_line as f32 * line_height, + height: line_height, + }) + } else { + None + } + }) + .map(|region| { + region + + Vector::new( + 0.0, + visual_lines_before_start as f32 * line_height + + scroll_offset, + ) + }) + .collect(); + + Cursor::Selection(regions) } _ => { let lines_before_cursor: usize = buffer @@ -332,3 +394,53 @@ impl PartialEq for Weak { } } } + +fn highlight_line<'a>( + line: &'a cosmic_text::BufferLine, + from: usize, + to: usize, +) -> impl Iterator + 'a { + let layout = line + .layout_opt() + .as_ref() + .expect("Line layout should be cached"); + + layout.iter().map(move |visual_line| { + let start = visual_line + .glyphs + .first() + .map(|glyph| glyph.start) + .unwrap_or(0); + let end = visual_line + .glyphs + .last() + .map(|glyph| glyph.end) + .unwrap_or(0); + + let range = start.max(from)..end.min(to); + + if range.is_empty() { + (0.0, 0.0) + } else if range.start == start && range.end == end { + (0.0, visual_line.w) + } else { + let first_glyph = visual_line + .glyphs + .iter() + .position(|glyph| range.start <= glyph.start) + .unwrap_or(0); + + let mut glyphs = visual_line.glyphs.iter(); + + let x = + glyphs.by_ref().take(first_glyph).map(|glyph| glyph.w).sum(); + + let width: f32 = glyphs + .take_while(|glyph| range.end > glyph.start) + .map(|glyph| glyph.w) + .sum(); + + (x, width) + } + }) +} diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index fcbd3dad..12e66f68 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -288,7 +288,11 @@ where for range in ranges { renderer.fill_quad( renderer::Quad { - bounds: range + Vector::new(bounds.x, bounds.y), + bounds: range + + Vector::new( + bounds.x + self.padding.left, + bounds.y + self.padding.top, + ), border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, -- cgit From d502c9f16fc78bf6b5253152751480c5b5e5999c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 13 Sep 2023 15:16:47 +0200 Subject: Unify `Focus` and `Click` updates in `widget::text_editor` --- widget/src/text_editor.rs | 48 ++++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 12e66f68..a8069069 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -189,16 +189,12 @@ where }; match update { - Update::Focus { click, action } => { - state.is_focused = true; - state.last_click = Some(click); - shell.publish(on_edit(action)); - } Update::Unfocus => { state.is_focused = false; state.is_dragging = false; } Update::Click { click, action } => { + state.is_focused = true; state.last_click = Some(click); state.is_dragging = true; shell.publish(on_edit(action)); @@ -340,9 +336,8 @@ where } enum Update { - Focus { click: mouse::Click, action: Action }, - Unfocus, Click { click: mouse::Click, action: Action }, + Unfocus, StopDragging, Edit(Action), Copy, @@ -364,31 +359,20 @@ impl Update { let cursor_position = cursor_position - Vector::new(padding.top, padding.left); - if state.is_focused { - let click = mouse::Click::new( - cursor_position, - state.last_click, - ); - - let action = match click.kind() { - mouse::click::Kind::Single => { - Action::Click(cursor_position) - } - mouse::click::Kind::Double => { - Action::SelectWord - } - mouse::click::Kind::Triple => { - Action::SelectLine - } - }; - - Some(Update::Click { click, action }) - } else { - Some(Update::Focus { - click: mouse::Click::new(cursor_position, None), - action: Action::Click(cursor_position), - }) - } + let click = mouse::Click::new( + cursor_position, + state.last_click, + ); + + let action = match click.kind() { + mouse::click::Kind::Single => { + Action::Click(cursor_position) + } + mouse::click::Kind::Double => Action::SelectWord, + mouse::click::Kind::Triple => Action::SelectLine, + }; + + Some(Update::Click { click, action }) } else if state.is_focused { Some(Update::Unfocus) } else { -- cgit From 52b36a9574f45138363a4bfc6394c6da03baa433 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 13 Sep 2023 15:17:04 +0200 Subject: Use `Theme::Dark` in `editor` example --- examples/editor/src/main.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index 50989ac5..2a70b34c 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -1,5 +1,5 @@ use iced::widget::{container, text_editor}; -use iced::{Element, Font, Sandbox, Settings}; +use iced::{Element, Font, Sandbox, Settings, Theme}; pub fn main() -> iced::Result { Editor::run(Settings::default()) @@ -46,4 +46,8 @@ impl Sandbox for Editor { .padding(20) .into() } + + fn theme(&self) -> Theme { + Theme::Dark + } } -- cgit From f4c51a96d50953d5fb6e9eb62194f226e2cbfd3c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 13 Sep 2023 16:11:43 +0200 Subject: Introduce `Motion` concept in `core::text::editor` --- core/src/text/editor.rs | 38 ++++++++++++---- graphics/src/text/editor.rs | 106 ++++++++++++++++++++++++++++++++++---------- widget/src/text_editor.rs | 77 ++++++++++++++++++-------------- 3 files changed, 156 insertions(+), 65 deletions(-) diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs index 09d4efde..f87e18f3 100644 --- a/core/src/text/editor.rs +++ b/core/src/text/editor.rs @@ -40,14 +40,8 @@ pub trait Editor: Sized + Default { #[derive(Debug, Clone, Copy, PartialEq)] pub enum Action { - MoveLeft, - MoveRight, - MoveUp, - MoveDown, - MoveLeftWord, - MoveRightWord, - MoveHome, - MoveEnd, + Move(Motion), + Select(Motion), SelectWord, SelectLine, Insert(char), @@ -58,6 +52,34 @@ pub enum Action { Drag(Point), } +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Motion { + Left, + Right, + Up, + Down, + WordLeft, + WordRight, + Home, + End, + PageUp, + PageDown, + DocumentStart, + DocumentEnd, +} + +impl Motion { + pub fn widen(self) -> Self { + match self { + Self::Left => Self::WordLeft, + Self::Right => Self::WordRight, + Self::Home => Self::DocumentStart, + Self::End => Self::DocumentEnd, + _ => self, + } + } +} + /// The cursor of an [`Editor`]. #[derive(Debug, Clone)] pub enum Cursor { diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 3544bde6..747f3a80 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -1,4 +1,4 @@ -use crate::core::text::editor::{self, Action, Cursor}; +use crate::core::text::editor::{self, Action, Cursor, Motion}; use crate::core::text::LineHeight; use crate::core::{Font, Pixels, Point, Rectangle, Size, Vector}; use crate::text; @@ -76,10 +76,7 @@ impl editor::Editor for Editor { let buffer = internal.editor.buffer(); match internal.editor.select_opt() { - Some(selection) - if cursor.line != selection.line - || cursor.index != selection.index => - { + Some(selection) => { let line_height = buffer.metrics().line_height; let scroll_offset = buffer.scroll() as f32 * line_height; @@ -236,26 +233,87 @@ impl editor::Editor for Editor { let editor = &mut internal.editor; - let mut act = |action| editor.action(font_system.raw(), action); - match action { - Action::MoveLeft => act(cosmic_text::Action::Left), - Action::MoveRight => act(cosmic_text::Action::Right), - Action::MoveUp => act(cosmic_text::Action::Up), - Action::MoveDown => act(cosmic_text::Action::Down), - Action::Insert(c) => act(cosmic_text::Action::Insert(c)), - Action::Enter => act(cosmic_text::Action::Enter), - Action::Backspace => act(cosmic_text::Action::Backspace), - Action::Delete => act(cosmic_text::Action::Delete), - Action::Click(position) => act(cosmic_text::Action::Click { - x: position.x as i32, - y: position.y as i32, - }), - Action::Drag(position) => act(cosmic_text::Action::Drag { - x: position.x as i32, - y: position.y as i32, - }), - _ => todo!(), + // Motion events + Action::Move(motion) => { + if let Some(_selection) = editor.select_opt() { + editor.set_select_opt(None); + } else { + editor.action( + font_system.raw(), + match motion { + Motion::Left => cosmic_text::Action::Left, + Motion::Right => cosmic_text::Action::Right, + Motion::Up => cosmic_text::Action::Up, + Motion::Down => cosmic_text::Action::Down, + Motion::WordLeft => cosmic_text::Action::LeftWord, + Motion::WordRight => cosmic_text::Action::RightWord, + Motion::Home => cosmic_text::Action::Home, + Motion::End => cosmic_text::Action::End, + Motion::PageUp => cosmic_text::Action::PageUp, + Motion::PageDown => cosmic_text::Action::PageDown, + Motion::DocumentStart => { + cosmic_text::Action::BufferStart + } + Motion::DocumentEnd => { + cosmic_text::Action::BufferEnd + } + }, + ); + } + } + + // Selection events + Action::Select(_motion) => todo!(), + Action::SelectWord => todo!(), + Action::SelectLine => todo!(), + + // Editing events + Action::Insert(c) => { + editor + .action(font_system.raw(), cosmic_text::Action::Insert(c)); + } + Action::Enter => { + editor.action(font_system.raw(), cosmic_text::Action::Enter); + } + Action::Backspace => { + editor + .action(font_system.raw(), cosmic_text::Action::Backspace); + } + Action::Delete => { + editor.action(font_system.raw(), cosmic_text::Action::Delete); + } + + // Mouse events + Action::Click(position) => { + editor.action( + font_system.raw(), + cosmic_text::Action::Click { + x: position.x as i32, + y: position.y as i32, + }, + ); + } + Action::Drag(position) => { + editor.action( + font_system.raw(), + cosmic_text::Action::Drag { + x: position.x as i32, + y: position.y as i32, + }, + ); + + // Deselect if selection matches cursor position + if let Some(selection) = editor.select_opt() { + let cursor = editor.cursor(); + + if cursor.line == selection.line + && cursor.index == selection.index + { + editor.set_select_opt(None); + } + } + } } editor.shape_as_needed(font_system.raw()); diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index a8069069..38c243bd 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -14,7 +14,7 @@ use crate::core::{ use std::cell::RefCell; pub use crate::style::text_editor::{Appearance, StyleSheet}; -pub use text::editor::Action; +pub use text::editor::{Action, Motion}; pub struct TextEditor<'a, Message, Renderer = crate::Renderer> where @@ -189,16 +189,16 @@ where }; match update { - Update::Unfocus => { - state.is_focused = false; - state.is_dragging = false; - } Update::Click { click, action } => { state.is_focused = true; - state.last_click = Some(click); state.is_dragging = true; + state.last_click = Some(click); shell.publish(on_edit(action)); } + Update::Unfocus => { + state.is_focused = false; + state.is_dragging = false; + } Update::StopDragging => { state.is_dragging = false; } @@ -352,6 +352,9 @@ impl Update { padding: Padding, cursor: mouse::Cursor, ) -> Option { + let edit = |action| Some(Update::Edit(action)); + let move_ = |motion| Some(Update::Edit(Action::Move(motion))); + match event { Event::Mouse(event) => match event { mouse::Event::ButtonPressed(mouse::Button::Left) => { @@ -386,7 +389,7 @@ impl Update { let cursor_position = cursor.position_in(bounds)? - Vector::new(padding.top, padding.left); - Some(Self::Edit(Action::Drag(cursor_position))) + edit(Action::Drag(cursor_position)) } _ => None, }, @@ -394,37 +397,31 @@ impl Update { keyboard::Event::KeyPressed { key_code, modifiers, - } if state.is_focused => match key_code { - keyboard::KeyCode::Left => { - if platform::is_jump_modifier_pressed(modifiers) { - Some(Self::Edit(Action::MoveLeftWord)) + } if state.is_focused => { + if let Some(motion) = motion(key_code) { + let motion = if modifiers.control() { + motion.widen() } else { - Some(Self::Edit(Action::MoveLeft)) - } - } - keyboard::KeyCode::Right => { - if platform::is_jump_modifier_pressed(modifiers) { - Some(Self::Edit(Action::MoveRightWord)) + motion + }; + + return edit(if modifiers.shift() { + Action::Select(motion) } else { - Some(Self::Edit(Action::MoveRight)) - } - } - keyboard::KeyCode::Up => Some(Self::Edit(Action::MoveUp)), - keyboard::KeyCode::Down => { - Some(Self::Edit(Action::MoveDown)) - } - keyboard::KeyCode::Enter => Some(Self::Edit(Action::Enter)), - keyboard::KeyCode::Backspace => { - Some(Self::Edit(Action::Backspace)) + Action::Move(motion) + }); } - keyboard::KeyCode::Delete => { - Some(Self::Edit(Action::Delete)) + + match key_code { + keyboard::KeyCode::Enter => edit(Action::Enter), + keyboard::KeyCode::Backspace => edit(Action::Backspace), + keyboard::KeyCode::Delete => edit(Action::Delete), + keyboard::KeyCode::Escape => Some(Self::Unfocus), + _ => None, } - keyboard::KeyCode::Escape => Some(Self::Unfocus), - _ => None, - }, + } keyboard::Event::CharacterReceived(c) if state.is_focused => { - Some(Self::Edit(Action::Insert(c))) + edit(Action::Insert(c)) } _ => None, }, @@ -433,6 +430,20 @@ impl Update { } } +fn motion(key_code: keyboard::KeyCode) -> Option { + match key_code { + keyboard::KeyCode::Left => Some(Motion::Left), + keyboard::KeyCode::Right => Some(Motion::Right), + keyboard::KeyCode::Up => Some(Motion::Up), + keyboard::KeyCode::Down => Some(Motion::Down), + keyboard::KeyCode::Home => Some(Motion::Home), + keyboard::KeyCode::End => Some(Motion::End), + keyboard::KeyCode::PageUp => Some(Motion::PageUp), + keyboard::KeyCode::PageDown => Some(Motion::PageDown), + _ => None, + } +} + mod platform { use crate::core::keyboard; -- cgit From f14ef7a6069cf45ae11261d7d20df6a5d7870dde Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 13 Sep 2023 16:31:56 +0200 Subject: Fix `clippy` lints --- graphics/src/text/editor.rs | 27 ++++++++++++++++++--------- widget/src/helpers.rs | 6 +++--- widget/src/text_editor.rs | 30 ++++++++++++++++++++---------- 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 747f3a80..d31ea390 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -25,7 +25,7 @@ impl Editor { } pub fn buffer(&self) -> &cosmic_text::Buffer { - &self.internal().editor.buffer() + self.internal().editor.buffer() } pub fn downgrade(&self) -> Weak { @@ -53,11 +53,11 @@ impl editor::Editor for Editor { line_height: 1.0, }); + let mut font_system = + text::font_system().write().expect("Write font system"); + buffer.set_text( - text::font_system() - .write() - .expect("Write font system") - .raw(), + font_system.raw(), text, cosmic_text::Attrs::new(), cosmic_text::Shaping::Advanced, @@ -65,6 +65,7 @@ impl editor::Editor for Editor { Editor(Some(Arc::new(Internal { editor: cosmic_text::Editor::new(buffer), + version: font_system.version(), ..Default::default() }))) } @@ -347,6 +348,14 @@ impl editor::Editor for Editor { let mut changed = false; + if font_system.version() != internal.version { + for line in internal.editor.buffer_mut().lines.iter_mut() { + line.reset(); + } + + changed = true; + } + if new_font != internal.font { for line in internal.editor.buffer_mut().lines.iter_mut() { let _ = line.set_attrs_list(cosmic_text::AttrsList::new( @@ -383,7 +392,7 @@ impl editor::Editor for Editor { } if changed { - internal.min_bounds = text::measure(&internal.editor.buffer()); + internal.min_bounds = text::measure(internal.editor.buffer()); } self.0 = Some(Arc::new(internal)); @@ -453,11 +462,11 @@ impl PartialEq for Weak { } } -fn highlight_line<'a>( - line: &'a cosmic_text::BufferLine, +fn highlight_line( + line: &cosmic_text::BufferLine, from: usize, to: usize, -) -> impl Iterator + 'a { +) -> impl Iterator + '_ { let layout = line .layout_opt() .as_ref() diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 61541eac..e3f31513 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -210,9 +210,9 @@ where /// Creates a new [`TextEditor`]. /// /// [`TextEditor`]: crate::TextEditor -pub fn text_editor<'a, Message, Renderer>( - content: &'a text_editor::Content, -) -> TextEditor<'a, Message, Renderer> +pub fn text_editor( + content: &text_editor::Content, +) -> TextEditor<'_, Message, Renderer> where Message: Clone, Renderer: core::text::Renderer, diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 38c243bd..48de6409 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -7,8 +7,8 @@ use crate::core::text::editor::{Cursor, Editor as _}; use crate::core::text::{self, LineHeight}; use crate::core::widget::{self, Widget}; use crate::core::{ - Clipboard, Color, Element, Length, Padding, Pixels, Point, Rectangle, - Shell, Vector, + Clipboard, Color, Element, Length, Padding, Pixels, Rectangle, Shell, + Vector, }; use std::cell::RefCell; @@ -205,8 +205,12 @@ where Update::Edit(action) => { shell.publish(on_edit(action)); } - Update::Copy => {} - Update::Paste => if let Some(_contents) = clipboard.read() {}, + Update::Copy => todo!(), + Update::Paste => { + if let Some(_contents) = clipboard.read() { + todo!() + } + } } event::Status::Captured @@ -353,7 +357,6 @@ impl Update { cursor: mouse::Cursor, ) -> Option { let edit = |action| Some(Update::Edit(action)); - let move_ = |motion| Some(Update::Edit(Action::Move(motion))); match event { Event::Mouse(event) => match event { @@ -399,11 +402,12 @@ impl Update { modifiers, } if state.is_focused => { if let Some(motion) = motion(key_code) { - let motion = if modifiers.control() { - motion.widen() - } else { - motion - }; + let motion = + if platform::is_jump_modifier_pressed(modifiers) { + motion.widen() + } else { + motion + }; return edit(if modifiers.shift() { Action::Select(motion) @@ -417,6 +421,12 @@ impl Update { keyboard::KeyCode::Backspace => edit(Action::Backspace), keyboard::KeyCode::Delete => edit(Action::Delete), keyboard::KeyCode::Escape => Some(Self::Unfocus), + keyboard::KeyCode::C => Some(Self::Copy), + keyboard::KeyCode::V + if modifiers.command() && !modifiers.alt() => + { + Some(Self::Paste) + } _ => None, } } -- cgit From c829b4b04e1274f157ea7bb3adf832c4c53ce3e8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 13 Sep 2023 17:55:33 +0200 Subject: Fix unused import in `iced_renderer` --- renderer/src/lib.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 6f044af6..81f60886 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -29,9 +29,7 @@ pub use geometry::Geometry; use crate::core::renderer; use crate::core::text::{self, Text}; -use crate::core::{ - Background, Color, Font, Pixels, Point, Rectangle, Size, Vector, -}; +use crate::core::{Background, Color, Font, Pixels, Point, Rectangle, Vector}; use crate::graphics::text::Editor; use crate::graphics::text::Paragraph; use crate::graphics::Mesh; @@ -219,7 +217,10 @@ impl text::Renderer for Renderer { impl crate::core::image::Renderer for Renderer { type Handle = crate::core::image::Handle; - fn dimensions(&self, handle: &crate::core::image::Handle) -> Size { + fn dimensions( + &self, + handle: &crate::core::image::Handle, + ) -> core::Size { delegate!(self, renderer, renderer.dimensions(handle)) } @@ -230,7 +231,7 @@ impl crate::core::image::Renderer for Renderer { #[cfg(feature = "svg")] impl crate::core::svg::Renderer for Renderer { - fn dimensions(&self, handle: &crate::core::svg::Handle) -> Size { + fn dimensions(&self, handle: &crate::core::svg::Handle) -> core::Size { delegate!(self, renderer, renderer.dimensions(handle)) } -- cgit From ab020383b9fd7f2cc15d145dd1a3c0870dc71d8b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 14 Sep 2023 00:41:15 +0200 Subject: Fix scrolling offset for `Cursor::Selection` --- graphics/src/text/editor.rs | 74 +++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 43 deletions(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index d31ea390..c0f8d9d5 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -1,6 +1,6 @@ use crate::core::text::editor::{self, Action, Cursor, Motion}; use crate::core::text::LineHeight; -use crate::core::{Font, Pixels, Point, Rectangle, Size, Vector}; +use crate::core::{Font, Pixels, Point, Rectangle, Size}; use crate::text; use cosmic_text::Edit; @@ -78,29 +78,18 @@ impl editor::Editor for Editor { match internal.editor.select_opt() { Some(selection) => { - let line_height = buffer.metrics().line_height; - let scroll_offset = buffer.scroll() as f32 * line_height; - let (start, end) = if cursor < selection { (cursor, selection) } else { (selection, cursor) }; - let visual_lines_before_start: usize = buffer - .lines - .iter() - .take(start.line) - .map(|line| { - line.layout_opt() - .as_ref() - .expect("Line layout should be cached") - .len() - }) - .sum(); - + let line_height = buffer.metrics().line_height; let selected_lines = end.line - start.line + 1; + let visual_lines_offset = + visual_lines_offset(start.line, buffer); + let regions = buffer .lines .iter() @@ -124,37 +113,24 @@ impl editor::Editor for Editor { Some(Rectangle { x, width, - y: visual_line as f32 * line_height, + y: (visual_line as i32 + visual_lines_offset) + as f32 + * line_height, height: line_height, }) } else { None } }) - .map(|region| { - region - + Vector::new( - 0.0, - visual_lines_before_start as f32 * line_height - + scroll_offset, - ) - }) .collect(); Cursor::Selection(regions) } _ => { - let lines_before_cursor: usize = buffer - .lines - .iter() - .take(cursor.line) - .map(|line| { - line.layout_opt() - .as_ref() - .expect("Line layout should be cached") - .len() - }) - .sum(); + let line_height = buffer.metrics().line_height; + + let visual_lines_offset = + visual_lines_offset(cursor.line, buffer); let line = buffer .lines @@ -168,7 +144,7 @@ impl editor::Editor for Editor { let mut lines = layout.iter().enumerate(); - let (subline, offset) = lines + let (visual_line, offset) = lines .find_map(|(i, line)| { let start = line .glyphs @@ -208,14 +184,10 @@ impl editor::Editor for Editor { layout.last().map(|line| line.w).unwrap_or(0.0), )); - let line_height = buffer.metrics().line_height; - - let scroll_offset = buffer.scroll() as f32 * line_height; - Cursor::Caret(Point::new( offset, - (lines_before_cursor + subline) as f32 * line_height - - scroll_offset, + (visual_lines_offset + visual_line as i32) as f32 + * line_height, )) } } @@ -511,3 +483,19 @@ fn highlight_line( } }) } + +fn visual_lines_offset(line: usize, buffer: &cosmic_text::Buffer) -> i32 { + let visual_lines_before_start: usize = buffer + .lines + .iter() + .take(line) + .map(|line| { + line.layout_opt() + .as_ref() + .expect("Line layout should be cached") + .len() + }) + .sum(); + + visual_lines_before_start as i32 - buffer.scroll() +} -- cgit From e6c2db8a9312e3fe37f30f049d1fa497892f1a86 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 14 Sep 2023 00:47:04 +0200 Subject: Fix `Cursor::Caret` position on lines that wrap on whitespace --- graphics/src/text/editor.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index c0f8d9d5..83d41c85 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -157,7 +157,7 @@ impl editor::Editor for Editor { .map(|glyph| glyph.end) .unwrap_or(0); - let is_cursor_after_start = start <= cursor.index; + let is_cursor_before_start = start > cursor.index; let is_cursor_before_end = match cursor.affinity { cosmic_text::Affinity::Before => { @@ -166,7 +166,17 @@ impl editor::Editor for Editor { cosmic_text::Affinity::After => cursor.index < end, }; - if is_cursor_after_start && is_cursor_before_end { + if is_cursor_before_start { + // Sometimes, the glyph we are looking for is right + // between lines. This can happen when a line wraps + // on a space. + // In that case, we can assume the cursor is at the + // end of the previous line. + // i is guaranteed to be > 0 because `start` is always + // 0 for the first line, so there is no way for the + // cursor to be before it. + Some((i - 1, layout[i - 1].w)) + } else if is_cursor_before_end { let offset = line .glyphs .iter() -- cgit From b24b94d82778733ddae1b824d0d7690afcec3056 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 14 Sep 2023 14:18:49 +0200 Subject: Handle motions when a selection is present in `text::Editor` --- core/src/text/editor.rs | 23 +++++++++++++++ graphics/src/text/editor.rs | 70 ++++++++++++++++++++++++++++++--------------- 2 files changed, 70 insertions(+), 23 deletions(-) diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs index f87e18f3..3adfc61a 100644 --- a/core/src/text/editor.rs +++ b/core/src/text/editor.rs @@ -78,6 +78,29 @@ impl Motion { _ => self, } } + + pub fn direction(&self) -> Direction { + match self { + Self::Left + | Self::Up + | Self::WordLeft + | Self::Home + | Self::PageUp + | Self::DocumentStart => Direction::Left, + Self::Right + | Self::Down + | Self::WordRight + | Self::End + | Self::PageDown + | Self::DocumentEnd => Direction::Right, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Left, + Right, } /// The cursor of an [`Editor`]. diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 83d41c85..d88bcd1d 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -1,4 +1,4 @@ -use crate::core::text::editor::{self, Action, Cursor, Motion}; +use crate::core::text::editor::{self, Action, Cursor, Direction, Motion}; use crate::core::text::LineHeight; use crate::core::{Font, Pixels, Point, Rectangle, Size}; use crate::text; @@ -219,30 +219,37 @@ impl editor::Editor for Editor { match action { // Motion events Action::Move(motion) => { - if let Some(_selection) = editor.select_opt() { + if let Some(selection) = editor.select_opt() { + let cursor = editor.cursor(); + + let (left, right) = if cursor < selection { + (cursor, selection) + } else { + (selection, cursor) + }; + editor.set_select_opt(None); + + match motion { + // These motions are performed as-is even when a selection + // is present + Motion::Home + | Motion::End + | Motion::DocumentStart + | Motion::DocumentEnd => { + editor.action( + font_system.raw(), + motion_to_action(motion), + ); + } + // Other motions simply move the cursor to one end of the selection + _ => editor.set_cursor(match motion.direction() { + Direction::Left => left, + Direction::Right => right, + }), + } } else { - editor.action( - font_system.raw(), - match motion { - Motion::Left => cosmic_text::Action::Left, - Motion::Right => cosmic_text::Action::Right, - Motion::Up => cosmic_text::Action::Up, - Motion::Down => cosmic_text::Action::Down, - Motion::WordLeft => cosmic_text::Action::LeftWord, - Motion::WordRight => cosmic_text::Action::RightWord, - Motion::Home => cosmic_text::Action::Home, - Motion::End => cosmic_text::Action::End, - Motion::PageUp => cosmic_text::Action::PageUp, - Motion::PageDown => cosmic_text::Action::PageDown, - Motion::DocumentStart => { - cosmic_text::Action::BufferStart - } - Motion::DocumentEnd => { - cosmic_text::Action::BufferEnd - } - }, - ); + editor.action(font_system.raw(), motion_to_action(motion)); } } @@ -509,3 +516,20 @@ fn visual_lines_offset(line: usize, buffer: &cosmic_text::Buffer) -> i32 { visual_lines_before_start as i32 - buffer.scroll() } + +fn motion_to_action(motion: Motion) -> cosmic_text::Action { + match motion { + Motion::Left => cosmic_text::Action::Left, + Motion::Right => cosmic_text::Action::Right, + Motion::Up => cosmic_text::Action::Up, + Motion::Down => cosmic_text::Action::Down, + Motion::WordLeft => cosmic_text::Action::LeftWord, + Motion::WordRight => cosmic_text::Action::RightWord, + Motion::Home => cosmic_text::Action::Home, + Motion::End => cosmic_text::Action::End, + Motion::PageUp => cosmic_text::Action::PageUp, + Motion::PageDown => cosmic_text::Action::PageDown, + Motion::DocumentStart => cosmic_text::Action::BufferStart, + Motion::DocumentEnd => cosmic_text::Action::BufferEnd, + } +} -- cgit From edd591847599a3e47601646ce075cb5b71ea751b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 14 Sep 2023 14:25:46 +0200 Subject: Implement motion selection in `text::Editor` --- graphics/src/text/editor.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index d88bcd1d..c6b2abd5 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -254,7 +254,26 @@ impl editor::Editor for Editor { } // Selection events - Action::Select(_motion) => todo!(), + Action::Select(motion) => { + let cursor = editor.cursor(); + + if editor.select_opt().is_none() { + editor.set_select_opt(Some(cursor)); + } + + editor.action(font_system.raw(), motion_to_action(motion)); + + // Deselect if selection matches cursor position + if let Some(selection) = editor.select_opt() { + let cursor = editor.cursor(); + + if cursor.line == selection.line + && cursor.index == selection.index + { + editor.set_select_opt(None); + } + } + } Action::SelectWord => todo!(), Action::SelectLine => todo!(), -- cgit From f7d66899f1ae087a87be5d084ec1ee9a03dd4ecc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 14 Sep 2023 15:20:23 +0200 Subject: Implement `Action::SelectWord` in `text::Editor` --- graphics/Cargo.toml | 1 + graphics/src/text/editor.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 26bd1435..3165810b 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -34,6 +34,7 @@ raw-window-handle.workspace = true rustc-hash.workspace = true thiserror.workspace = true twox-hash.workspace = true +unicode-segmentation.workspace = true image.workspace = true image.optional = true diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index c6b2abd5..3fd2c4fe 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -274,7 +274,66 @@ impl editor::Editor for Editor { } } } - Action::SelectWord => todo!(), + Action::SelectWord => { + use unicode_segmentation::UnicodeSegmentation; + + let cursor = editor.cursor(); + + if let Some(line) = editor.buffer().lines.get(cursor.line) { + let (start, end) = + UnicodeSegmentation::unicode_word_indices(line.text()) + // Split words with dots + .flat_map(|(i, word)| { + word.split('.').scan(i, |current, word| { + let start = *current; + *current += word.len() + 1; + + Some((start, word)) + }) + }) + // Turn words into ranges + .map(|(i, word)| (i, i + word.len())) + // Find the word at cursor + .find(|&(start, end)| { + start <= cursor.index && cursor.index < end + }) + // Cursor is not in a word. Let's select its punctuation cluster. + .unwrap_or_else(|| { + let start = line.text()[..cursor.index] + .char_indices() + .rev() + .take_while(|(_, c)| { + c.is_ascii_punctuation() + }) + .map(|(i, _)| i) + .last() + .unwrap_or(cursor.index); + + let end = line.text()[cursor.index..] + .char_indices() + .skip_while(|(_, c)| { + c.is_ascii_punctuation() + }) + .map(|(i, _)| i + cursor.index) + .next() + .unwrap_or(cursor.index); + + (start, end) + }); + + if start != end { + editor.set_cursor(cosmic_text::Cursor { + index: start, + ..cursor + }); + + editor.set_select_opt(Some(cosmic_text::Cursor { + index: end, + ..cursor + })); + } + } + } Action::SelectLine => todo!(), // Editing events -- cgit From 8cad1d682a306071f1f03bff4e70196adc946491 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 14 Sep 2023 15:23:20 +0200 Subject: Implement `Action::SelectLine` in `text::Editor` --- graphics/src/text/editor.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 3fd2c4fe..8eec94c9 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -334,7 +334,24 @@ impl editor::Editor for Editor { } } } - Action::SelectLine => todo!(), + Action::SelectLine => { + let cursor = editor.cursor(); + + if let Some(line_length) = editor + .buffer() + .lines + .get(cursor.line) + .map(|line| line.text().len()) + { + editor + .set_cursor(cosmic_text::Cursor { index: 0, ..cursor }); + + editor.set_select_opt(Some(cosmic_text::Cursor { + index: line_length, + ..cursor + })); + } + } // Editing events Action::Insert(c) => { -- cgit From c7d02e24e6f8265c205a68bd97b2643d40ae30ee Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 14 Sep 2023 18:57:09 +0200 Subject: Remove `Editor::min_bounds` and use `bounds` instead --- core/src/renderer/null.rs | 4 ---- core/src/text/editor.rs | 14 -------------- graphics/src/text/editor.rs | 4 ---- tiny_skia/src/text.rs | 2 +- wgpu/src/text.rs | 2 +- 5 files changed, 2 insertions(+), 24 deletions(-) diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index adf75969..e714e492 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -131,10 +131,6 @@ impl text::Editor for () { Size::ZERO } - fn min_bounds(&self) -> Size { - Size::ZERO - } - fn update( &mut self, _new_bounds: Size, diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs index 3adfc61a..56cda3ef 100644 --- a/core/src/text/editor.rs +++ b/core/src/text/editor.rs @@ -14,10 +14,6 @@ pub trait Editor: Sized + Default { /// Returns the current boundaries of the [`Editor`]. fn bounds(&self) -> Size; - /// Returns the minimum boundaries that can fit the contents of the - /// [`Editor`]. - fn min_bounds(&self) -> Size; - /// Updates the [`Editor`] with some new attributes. fn update( &mut self, @@ -26,16 +22,6 @@ pub trait Editor: Sized + Default { new_size: Pixels, new_line_height: LineHeight, ); - - /// Returns the minimum width that can fit the contents of the [`Editor`]. - fn min_width(&self) -> f32 { - self.min_bounds().width - } - - /// Returns the minimum height that can fit the contents of the [`Editor`]. - fn min_height(&self) -> f32 { - self.min_bounds().height - } } #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 8eec94c9..6d9e9bb6 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -410,10 +410,6 @@ impl editor::Editor for Editor { self.internal().bounds } - fn min_bounds(&self) -> Size { - self.internal().min_bounds - } - fn update( &mut self, new_bounds: Size, diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index d055c749..96cfbf32 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -86,7 +86,7 @@ impl Pipeline { font_system.raw(), &mut self.glyph_cache, editor.buffer(), - Rectangle::new(position, editor.min_bounds()), + Rectangle::new(position, editor.bounds()), color, alignment::Horizontal::Left, alignment::Vertical::Top, diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 397c38dd..581df0cb 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -151,7 +151,7 @@ impl Pipeline { ( editor.buffer(), - Rectangle::new(*position, editor.min_bounds()), + Rectangle::new(*position, editor.bounds()), alignment::Horizontal::Left, alignment::Vertical::Top, *color, -- cgit From 3afac11784b9cedc7e6208e3bf1d0365e1f5e902 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 14 Sep 2023 18:58:52 +0200 Subject: Remove `min_bounds` field in `graphics::text::Editor` --- graphics/src/text/editor.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 6d9e9bb6..07a2d72a 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -15,7 +15,6 @@ struct Internal { editor: cosmic_text::Editor, font: Font, bounds: Size, - min_bounds: Size, version: text::Version, } @@ -426,14 +425,10 @@ impl editor::Editor for Editor { let mut font_system = text::font_system().write().expect("Write font system"); - let mut changed = false; - if font_system.version() != internal.version { for line in internal.editor.buffer_mut().lines.iter_mut() { line.reset(); } - - changed = true; } if new_font != internal.font { @@ -442,8 +437,6 @@ impl editor::Editor for Editor { text::to_attributes(new_font), )); } - - changed = true; } let metrics = internal.editor.buffer().metrics(); @@ -456,8 +449,6 @@ impl editor::Editor for Editor { font_system.raw(), cosmic_text::Metrics::new(new_size.0, new_line_height.0), ); - - changed = true; } if new_bounds != internal.bounds { @@ -468,11 +459,6 @@ impl editor::Editor for Editor { ); internal.bounds = new_bounds; - changed = true; - } - - if changed { - internal.min_bounds = text::measure(internal.editor.buffer()); } self.0 = Some(Arc::new(internal)); @@ -489,7 +475,6 @@ impl PartialEq for Internal { fn eq(&self, other: &Self) -> bool { self.font == other.font && self.bounds == other.bounds - && self.min_bounds == other.min_bounds && self.editor.buffer().metrics() == other.editor.buffer().metrics() } } @@ -505,7 +490,6 @@ impl Default for Internal { )), font: Font::default(), bounds: Size::ZERO, - min_bounds: Size::ZERO, version: text::Version::default(), } } @@ -516,7 +500,6 @@ impl fmt::Debug for Internal { f.debug_struct("Internal") .field("font", &self.font) .field("bounds", &self.bounds) - .field("min_bounds", &self.min_bounds) .finish() } } -- cgit From 8e6e37e0cee79a2f293abedd18a6a7249575bb63 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 14 Sep 2023 19:05:50 +0200 Subject: Fix broken intra-doc links --- core/src/text.rs | 2 ++ widget/src/lib.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/core/src/text.rs b/core/src/text.rs index 5aacbcc5..90581fea 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -137,6 +137,8 @@ impl Hit { /// /// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some /// [`Text`]. +/// +/// [`compare`]: Paragraph::compare #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Difference { /// No difference. diff --git a/widget/src/lib.rs b/widget/src/lib.rs index f8e5e865..4c318d75 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -93,6 +93,8 @@ pub use space::Space; #[doc(no_inline)] pub use text::Text; #[doc(no_inline)] +pub use text_editor::TextEditor; +#[doc(no_inline)] pub use text_input::TextInput; #[doc(no_inline)] pub use toggler::Toggler; -- cgit From 2c782bbe7a048f6f091e15f68de29a846b9bb059 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 14 Sep 2023 19:35:29 +0200 Subject: Fix width of horizontal scrollbar in `Scrollable` --- widget/src/scrollable.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index def28821..7b1d7a30 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1399,8 +1399,8 @@ impl Scrollbars { // Need to adjust the width of the horizontal scrollbar if the vertical scrollbar // is present - let scrollbar_y_width = show_scrollbar_y - .map_or(0.0, |v| v.width.max(v.scroller_width) + v.margin); + let scrollbar_y_width = y_scrollbar + .map_or(0.0, |scrollbar| scrollbar.total_bounds.width); let total_scrollbar_height = width.max(scroller_width) + 2.0 * margin; @@ -1425,12 +1425,12 @@ impl Scrollbars { let ratio = bounds.width / content_bounds.width; // min width for easier grabbing with extra wide content - let scroller_length = (bounds.width * ratio).max(2.0); - let scroller_offset = translation.x * ratio; + let scroller_length = (scrollbar_bounds.width * ratio).max(2.0); + let scroller_offset = + translation.x * ratio * scrollbar_bounds.width / bounds.width; let scroller_bounds = Rectangle { - x: (scrollbar_bounds.x + scroller_offset - scrollbar_y_width) - .max(0.0), + x: (scrollbar_bounds.x + scroller_offset).max(0.0), y: bounds.y + bounds.height - total_scrollbar_height / 2.0 - scroller_width / 2.0, -- cgit From bebb2b0252cba7af3641e28a12de2c6db46b8946 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 14 Sep 2023 19:37:15 +0200 Subject: Fix styling of horizontal scrollbar in `scrollable` example --- examples/scrollable/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index 8c08d993..21e69284 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -389,12 +389,12 @@ impl scrollable::StyleSheet for ScrollbarCustomStyle { background: style .active(&theme::Scrollable::default()) .background, - border_radius: 0.0.into(), + border_radius: 2.0.into(), border_width: 0.0, border_color: Default::default(), scroller: Scroller { color: Color::from_rgb8(250, 85, 134), - border_radius: 0.0.into(), + border_radius: 2.0.into(), border_width: 0.0, border_color: Default::default(), }, -- cgit From f7fc13d98c52a9260b1ab55394a0c3d2693318ed Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 14 Sep 2023 22:55:54 +0200 Subject: Fix `Copy` action being triggered without any modifiers --- widget/src/text_editor.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 48de6409..114d35ef 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -421,7 +421,9 @@ impl Update { keyboard::KeyCode::Backspace => edit(Action::Backspace), keyboard::KeyCode::Delete => edit(Action::Delete), keyboard::KeyCode::Escape => Some(Self::Unfocus), - keyboard::KeyCode::C => Some(Self::Copy), + keyboard::KeyCode::C if modifiers.command() => { + Some(Self::Copy) + } keyboard::KeyCode::V if modifiers.command() && !modifiers.alt() => { -- cgit From ed11b04f6054809ea55ec9b3f0bd221ac2caf9ca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 14 Sep 2023 22:59:02 +0200 Subject: Fix `height` of vertical scroller in `Scrollbar` --- widget/src/scrollable.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 7b1d7a30..f92e6223 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1364,15 +1364,15 @@ impl Scrollbars { let ratio = bounds.height / content_bounds.height; // min height for easier grabbing with super tall content - let scroller_height = (bounds.height * ratio).max(2.0); - let scroller_offset = translation.y * ratio; + let scroller_height = (scrollbar_bounds.height * ratio).max(2.0); + let scroller_offset = + translation.y * ratio * scrollbar_bounds.height / bounds.height; let scroller_bounds = Rectangle { x: bounds.x + bounds.width - total_scrollbar_width / 2.0 - scroller_width / 2.0, - y: (scrollbar_bounds.y + scroller_offset - x_scrollbar_height) - .max(0.0), + y: (scrollbar_bounds.y + scroller_offset).max(0.0), width: scroller_width, height: scroller_height, }; -- cgit From c6d0443627c22dcf1576303e5a426aa3622f1b7d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 16 Sep 2023 15:27:25 +0200 Subject: Implement methods to query the contents of a `TextEditor` --- core/src/renderer/null.rs | 12 ++++++++++++ core/src/text/editor.rs | 6 ++++++ graphics/src/text/editor.rs | 41 ++++++++++++++++++++++++++++++++++++++ widget/src/text_editor.rs | 48 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+) diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index e714e492..01a52c7a 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -125,6 +125,18 @@ impl text::Editor for () { text::editor::Cursor::Caret(Point::ORIGIN) } + fn selection(&self) -> Option { + None + } + + fn line(&self, _index: usize) -> Option<&str> { + None + } + + fn line_count(&self) -> usize { + 0 + } + fn perform(&mut self, _action: text::editor::Action) {} fn bounds(&self) -> Size { diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs index 56cda3ef..5532fac5 100644 --- a/core/src/text/editor.rs +++ b/core/src/text/editor.rs @@ -9,6 +9,12 @@ pub trait Editor: Sized + Default { fn cursor(&self) -> Cursor; + fn selection(&self) -> Option; + + fn line(&self, index: usize) -> Option<&str>; + + fn line_count(&self) -> usize; + fn perform(&mut self, action: Action); /// Returns the current boundaries of the [`Editor`]. diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 07a2d72a..1e375a25 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -69,6 +69,47 @@ impl editor::Editor for Editor { }))) } + fn line(&self, index: usize) -> Option<&str> { + self.buffer() + .lines + .get(index) + .map(cosmic_text::BufferLine::text) + } + + fn line_count(&self) -> usize { + self.buffer().lines.len() + } + + fn selection(&self) -> Option { + let internal = self.internal(); + + let cursor = internal.editor.cursor(); + let selection = internal.editor.select_opt()?; + + let (start, end) = if cursor < selection { + (cursor, selection) + } else { + (selection, cursor) + }; + + Some( + internal.editor.buffer().lines[start.line..=end.line] + .iter() + .enumerate() + .map(|(i, line)| { + if i == 0 { + &line.text()[start.index..] + } else if i == end.line - start.line { + &line.text()[..end.index] + } else { + line.text() + } + }) + .collect::>() + .join("\n"), + ) + } + fn cursor(&self) -> editor::Cursor { let internal = self.internal(); diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 114d35ef..ec7a6d1d 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -100,6 +100,54 @@ where internal.editor.perform(action); internal.is_dirty = true; } + + pub fn line_count(&self) -> usize { + self.0.borrow().editor.line_count() + } + + pub fn line( + &self, + index: usize, + ) -> Option + '_> { + std::cell::Ref::filter_map(self.0.borrow(), |internal| { + internal.editor.line(index) + }) + .ok() + } + + pub fn lines( + &self, + ) -> impl Iterator + '_> { + struct Lines<'a, Renderer: text::Renderer> { + internal: std::cell::Ref<'a, Internal>, + current: usize, + } + + impl<'a, Renderer: text::Renderer> Iterator for Lines<'a, Renderer> { + type Item = std::cell::Ref<'a, str>; + + fn next(&mut self) -> Option { + let line = std::cell::Ref::filter_map( + std::cell::Ref::clone(&self.internal), + |internal| internal.editor.line(self.current), + ) + .ok()?; + + self.current += 1; + + Some(line) + } + } + + Lines { + internal: self.0.borrow(), + current: 0, + } + } + + pub fn selection(&self) -> Option { + self.0.borrow().editor.selection() + } } impl Default for Content -- cgit From d051f21597bb333ac10183aaa3214a292e9aa365 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 16 Sep 2023 15:40:16 +0200 Subject: Implement `Copy` and `Paste` actions for `text::Editor` --- core/src/text/editor.rs | 5 ++++- examples/editor/src/main.rs | 2 +- graphics/src/text/editor.rs | 11 +++++++++++ widget/src/text_editor.rs | 11 ++++++++--- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs index 5532fac5..003557c1 100644 --- a/core/src/text/editor.rs +++ b/core/src/text/editor.rs @@ -1,6 +1,8 @@ use crate::text::LineHeight; use crate::{Pixels, Point, Rectangle, Size}; +use std::sync::Arc; + pub trait Editor: Sized + Default { type Font: Copy + PartialEq + Default; @@ -30,13 +32,14 @@ pub trait Editor: Sized + Default { ); } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum Action { Move(Motion), Select(Motion), SelectWord, SelectLine, Insert(char), + Paste(Arc), Enter, Backspace, Delete, diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index 2a70b34c..11819c69 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -9,7 +9,7 @@ struct Editor { content: text_editor::Content, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] enum Message { Edit(text_editor::Action), } diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 1e375a25..1890cb82 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -398,6 +398,17 @@ impl editor::Editor for Editor { editor .action(font_system.raw(), cosmic_text::Action::Insert(c)); } + Action::Paste(text) => { + editor.insert_string(&text, None); + + // TODO: Fix cosmic-text + // Cursor should be marked as moved after `insert_string`. + let cursor = editor.cursor(); + + editor + .buffer_mut() + .shape_until_cursor(font_system.raw(), cursor); + } Action::Enter => { editor.action(font_system.raw(), cosmic_text::Action::Enter); } diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index ec7a6d1d..0bb6b7d3 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -12,6 +12,7 @@ use crate::core::{ }; use std::cell::RefCell; +use std::sync::Arc; pub use crate::style::text_editor::{Appearance, StyleSheet}; pub use text::editor::{Action, Motion}; @@ -253,10 +254,14 @@ where Update::Edit(action) => { shell.publish(on_edit(action)); } - Update::Copy => todo!(), + Update::Copy => { + if let Some(selection) = self.content.selection() { + clipboard.write(selection); + } + } Update::Paste => { - if let Some(_contents) = clipboard.read() { - todo!() + if let Some(contents) = clipboard.read() { + shell.publish(on_edit(Action::Paste(Arc::new(contents)))); } } } -- cgit From c9dbccba468da683af2513535c40374da804aa60 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 16 Sep 2023 16:27:02 +0200 Subject: Use fork of `cosmic-text` with some minor fixes --- Cargo.toml | 4 ++++ graphics/src/text/editor.rs | 36 +----------------------------------- 2 files changed, 5 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index af74a3cf..f8dd5f14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -151,3 +151,7 @@ wgpu = "0.17" winapi = "0.3" window_clipboard = "0.3" winit = { git = "https://github.com/iced-rs/winit.git", rev = "c52db2045d0a2f1b8d9923870de1d4ab1994146e", default-features = false } + +[patch.crates-io.cosmic-text] +git = "https://github.com/hecrj/cosmic-text.git" +rev = "cb83458e7d0b84ef37c5beb72dda5046d7d343a6" diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 1890cb82..a828a3bc 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -81,33 +81,7 @@ impl editor::Editor for Editor { } fn selection(&self) -> Option { - let internal = self.internal(); - - let cursor = internal.editor.cursor(); - let selection = internal.editor.select_opt()?; - - let (start, end) = if cursor < selection { - (cursor, selection) - } else { - (selection, cursor) - }; - - Some( - internal.editor.buffer().lines[start.line..=end.line] - .iter() - .enumerate() - .map(|(i, line)| { - if i == 0 { - &line.text()[start.index..] - } else if i == end.line - start.line { - &line.text()[..end.index] - } else { - line.text() - } - }) - .collect::>() - .join("\n"), - ) + self.internal().editor.copy_selection() } fn cursor(&self) -> editor::Cursor { @@ -400,14 +374,6 @@ impl editor::Editor for Editor { } Action::Paste(text) => { editor.insert_string(&text, None); - - // TODO: Fix cosmic-text - // Cursor should be marked as moved after `insert_string`. - let cursor = editor.cursor(); - - editor - .buffer_mut() - .shape_until_cursor(font_system.raw(), cursor); } Action::Enter => { editor.action(font_system.raw(), cosmic_text::Action::Enter); -- cgit From 45c5cfe5774ac99a6e1b1d1014418f68b21b41cf Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 16 Sep 2023 19:05:31 +0200 Subject: Avoid drag on double or triple click for now in `TextEditor` --- core/src/mouse/click.rs | 4 ++++ widget/src/text_editor.rs | 52 +++++++++++++++++++++++++---------------------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/core/src/mouse/click.rs b/core/src/mouse/click.rs index 4a7d796c..e8e5fb56 100644 --- a/core/src/mouse/click.rs +++ b/core/src/mouse/click.rs @@ -61,6 +61,10 @@ impl Click { self.kind } + pub fn position(&self) -> Point { + self.position + } + fn is_consecutive(&self, new_position: Point, time: Instant) -> bool { let duration = if time > self.time { Some(time - self.time) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 0bb6b7d3..68e3c656 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -162,8 +162,8 @@ where struct State { is_focused: bool, - is_dragging: bool, last_click: Option, + drag_click: Option, } impl<'a, Message, Renderer> Widget @@ -179,8 +179,8 @@ where fn state(&self) -> widget::tree::State { widget::tree::State::new(State { is_focused: false, - is_dragging: false, last_click: None, + drag_click: None, }) } @@ -238,18 +238,27 @@ where }; match update { - Update::Click { click, action } => { + Update::Click(click) => { + let action = match click.kind() { + mouse::click::Kind::Single => { + Action::Click(click.position()) + } + mouse::click::Kind::Double => Action::SelectWord, + mouse::click::Kind::Triple => Action::SelectLine, + }; + state.is_focused = true; - state.is_dragging = true; state.last_click = Some(click); + state.drag_click = Some(click.kind()); + shell.publish(on_edit(action)); } Update::Unfocus => { state.is_focused = false; - state.is_dragging = false; + state.drag_click = None; } - Update::StopDragging => { - state.is_dragging = false; + Update::Release => { + state.drag_click = None; } Update::Edit(action) => { shell.publish(on_edit(action)); @@ -393,9 +402,9 @@ where } enum Update { - Click { click: mouse::Click, action: Action }, + Click(mouse::Click), Unfocus, - StopDragging, + Release, Edit(Action), Copy, Paste, @@ -423,15 +432,7 @@ impl Update { state.last_click, ); - let action = match click.kind() { - mouse::click::Kind::Single => { - Action::Click(cursor_position) - } - mouse::click::Kind::Double => Action::SelectWord, - mouse::click::Kind::Triple => Action::SelectLine, - }; - - Some(Update::Click { click, action }) + Some(Update::Click(click)) } else if state.is_focused { Some(Update::Unfocus) } else { @@ -439,14 +440,17 @@ impl Update { } } mouse::Event::ButtonReleased(mouse::Button::Left) => { - Some(Update::StopDragging) + Some(Update::Release) } - mouse::Event::CursorMoved { .. } if state.is_dragging => { - let cursor_position = cursor.position_in(bounds)? - - Vector::new(padding.top, padding.left); + mouse::Event::CursorMoved { .. } => match state.drag_click { + Some(mouse::click::Kind::Single) => { + let cursor_position = cursor.position_in(bounds)? + - Vector::new(padding.top, padding.left); - edit(Action::Drag(cursor_position)) - } + edit(Action::Drag(cursor_position)) + } + _ => None, + }, _ => None, }, Event::Keyboard(event) => match event { -- cgit From 723111bb0df486bffaedcaed0722b1793d65bfe3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 16 Sep 2023 19:09:31 +0200 Subject: Remove unnecessary `into_iter` call in `graphics::text` --- graphics/src/text.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/graphics/src/text.rs b/graphics/src/text.rs index 280e4f01..b4aeb2be 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -21,12 +21,11 @@ pub fn font_system() -> &'static RwLock { FONT_SYSTEM.get_or_init(|| { RwLock::new(FontSystem { - raw: cosmic_text::FontSystem::new_with_fonts( - [cosmic_text::fontdb::Source::Binary(Arc::new( + raw: cosmic_text::FontSystem::new_with_fonts([ + cosmic_text::fontdb::Source::Binary(Arc::new( include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), - ))] - .into_iter(), - ), + )), + ]), version: Version::default(), }) }) -- cgit From 76dc82e8e8b5201ec10f8d00d851c1decf998583 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 17 Sep 2023 15:29:14 +0200 Subject: Draft `Highlighter` API --- core/src/renderer/null.rs | 11 ++++++++ core/src/text.rs | 2 ++ core/src/text/editor.rs | 9 ++++++ core/src/text/highlighter.rs | 56 ++++++++++++++++++++++++++++++++++++ graphics/src/text.rs | 8 +++++- graphics/src/text/editor.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++ style/src/lib.rs | 2 +- style/src/text_editor.rs | 16 ++++++++++- widget/src/helpers.rs | 2 +- widget/src/text_editor.rs | 64 ++++++++++++++++++++++++++++++++---------- 10 files changed, 218 insertions(+), 19 deletions(-) create mode 100644 core/src/text/highlighter.rs diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index 01a52c7a..21597c8e 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -149,6 +149,17 @@ impl text::Editor for () { _new_font: Self::Font, _new_size: Pixels, _new_line_height: text::LineHeight, + _new_highlighter: &mut impl text::Highlighter, + ) { + } + + fn highlight( + &mut self, + _font: Self::Font, + _highlighter: &mut H, + _format_highlight: impl Fn( + &H::Highlight, + ) -> text::highlighter::Format, ) { } } diff --git a/core/src/text.rs b/core/src/text.rs index 90581fea..9b9c753c 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -2,8 +2,10 @@ mod paragraph; pub mod editor; +pub mod highlighter; pub use editor::Editor; +pub use highlighter::Highlighter; pub use paragraph::Paragraph; use crate::alignment; diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs index 003557c1..0f439c8d 100644 --- a/core/src/text/editor.rs +++ b/core/src/text/editor.rs @@ -1,3 +1,4 @@ +use crate::text::highlighter::{self, Highlighter}; use crate::text::LineHeight; use crate::{Pixels, Point, Rectangle, Size}; @@ -29,6 +30,14 @@ pub trait Editor: Sized + Default { new_font: Self::Font, new_size: Pixels, new_line_height: LineHeight, + new_highlighter: &mut impl Highlighter, + ); + + fn highlight( + &mut self, + font: Self::Font, + highlighter: &mut H, + format_highlight: impl Fn(&H::Highlight) -> highlighter::Format, ); } diff --git a/core/src/text/highlighter.rs b/core/src/text/highlighter.rs new file mode 100644 index 00000000..1f9ac840 --- /dev/null +++ b/core/src/text/highlighter.rs @@ -0,0 +1,56 @@ +use crate::Color; + +use std::hash::Hash; +use std::ops::Range; + +pub trait Highlighter: Clone + 'static { + type Settings: Hash; + type Highlight; + + type Iterator<'a>: Iterator, Self::Highlight)> + where + Self: 'a; + + fn new(settings: &Self::Settings) -> Self; + + fn change_line(&mut self, line: usize); + + fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_>; + + fn current_line(&self) -> usize; +} + +#[derive(Debug, Clone, Copy)] +pub struct Style { + pub color: Color, +} + +#[derive(Debug, Clone, Copy)] +pub struct PlainText; + +impl Highlighter for PlainText { + type Settings = (); + type Highlight = (); + + type Iterator<'a> = std::iter::Empty<(Range, Self::Highlight)>; + + fn new(_settings: &Self::Settings) -> Self { + Self + } + + fn change_line(&mut self, _line: usize) {} + + fn highlight_line(&mut self, _line: &str) -> Self::Iterator<'_> { + std::iter::empty() + } + + fn current_line(&self) -> usize { + usize::MAX + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Format { + pub color: Option, + pub font: Option, +} diff --git a/graphics/src/text.rs b/graphics/src/text.rs index b4aeb2be..5fcfc699 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -10,7 +10,7 @@ pub use cosmic_text; use crate::core::font::{self, Font}; use crate::core::text::Shaping; -use crate::core::Size; +use crate::core::{Color, Size}; use once_cell::sync::OnceCell; use std::borrow::Cow; @@ -129,3 +129,9 @@ pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping { Shaping::Advanced => cosmic_text::Shaping::Advanced, } } + +pub fn to_color(color: Color) -> cosmic_text::Color { + let [r, g, b, a] = color.into_rgba8(); + + cosmic_text::Color::rgba(r, g, b, a) +} diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index a828a3bc..901b4295 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -1,4 +1,5 @@ use crate::core::text::editor::{self, Action, Cursor, Direction, Motion}; +use crate::core::text::highlighter::{self, Highlighter}; use crate::core::text::LineHeight; use crate::core::{Font, Pixels, Point, Rectangle, Size}; use crate::text; @@ -15,6 +16,7 @@ struct Internal { editor: cosmic_text::Editor, font: Font, bounds: Size, + topmost_line_changed: Option, version: text::Version, } @@ -433,6 +435,7 @@ impl editor::Editor for Editor { new_font: Font, new_size: Pixels, new_line_height: LineHeight, + new_highlighter: &mut impl Highlighter, ) { let editor = self.0.take().expect("editor should always be initialized"); @@ -479,6 +482,69 @@ impl editor::Editor for Editor { internal.bounds = new_bounds; } + if let Some(topmost_line_changed) = internal.topmost_line_changed.take() + { + new_highlighter.change_line(topmost_line_changed); + } + + self.0 = Some(Arc::new(internal)); + } + + fn highlight( + &mut self, + font: Self::Font, + highlighter: &mut H, + format_highlight: impl Fn(&H::Highlight) -> highlighter::Format, + ) { + let internal = self.internal(); + + let scroll = internal.editor.buffer().scroll(); + let visible_lines = internal.editor.buffer().visible_lines(); + let last_visible_line = (scroll + visible_lines - 1) as usize; + + let current_line = highlighter.current_line(); + + if current_line > last_visible_line { + return; + } + + let editor = + self.0.take().expect("editor should always be initialized"); + + let mut internal = Arc::try_unwrap(editor) + .expect("Editor cannot have multiple strong references"); + + let mut font_system = + text::font_system().write().expect("Write font system"); + + let attributes = text::to_attributes(font); + + for line in &mut internal.editor.buffer_mut().lines + [current_line..=last_visible_line] + { + let mut list = cosmic_text::AttrsList::new(attributes); + + for (range, highlight) in highlighter.highlight_line(line.text()) { + let format = format_highlight(&highlight); + + list.add_span( + range, + cosmic_text::Attrs { + color_opt: format.color.map(text::to_color), + ..if let Some(font) = format.font { + text::to_attributes(font) + } else { + attributes + } + }, + ); + } + + let _ = line.set_attrs_list(list); + } + + internal.editor.shape_as_needed(font_system.raw()); + self.0 = Some(Arc::new(internal)); } } @@ -508,6 +574,7 @@ impl Default for Internal { )), font: Font::default(), bounds: Size::ZERO, + topmost_line_changed: None, version: text::Version::default(), } } diff --git a/style/src/lib.rs b/style/src/lib.rs index 7a97ac77..c9879f24 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -15,7 +15,7 @@ clippy::needless_borrow, clippy::new_without_default, clippy::useless_conversion, - missing_docs, + // missing_docs, unused_results, rustdoc::broken_intra_doc_links )] diff --git a/style/src/text_editor.rs b/style/src/text_editor.rs index 45c9bad8..f1c31287 100644 --- a/style/src/text_editor.rs +++ b/style/src/text_editor.rs @@ -1,5 +1,6 @@ //! Change the appearance of a text editor. -use iced_core::{Background, BorderRadius, Color}; +use crate::core::text::highlighter; +use crate::core::{self, Background, BorderRadius, Color}; /// The appearance of a text input. #[derive(Debug, Clone, Copy)] @@ -45,3 +46,16 @@ pub trait StyleSheet { /// Produces the style of a disabled text input. fn disabled(&self, style: &Self::Style) -> Appearance; } + +pub trait Highlight { + fn format(&self, theme: &Theme) -> highlighter::Format; +} + +impl Highlight for () { + fn format(&self, _theme: &Theme) -> highlighter::Format { + highlighter::Format { + color: None, + font: None, + } + } +} diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index e3f31513..e0b58722 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -212,7 +212,7 @@ where /// [`TextEditor`]: crate::TextEditor pub fn text_editor( content: &text_editor::Content, -) -> TextEditor<'_, Message, Renderer> +) -> TextEditor<'_, core::text::highlighter::PlainText, Message, Renderer> where Message: Clone, Renderer: core::text::Renderer, diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 68e3c656..b17e1156 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -4,6 +4,7 @@ use crate::core::layout::{self, Layout}; use crate::core::mouse; use crate::core::renderer; use crate::core::text::editor::{Cursor, Editor as _}; +use crate::core::text::highlighter::{self, Highlighter}; use crate::core::text::{self, LineHeight}; use crate::core::widget::{self, Widget}; use crate::core::{ @@ -12,13 +13,15 @@ use crate::core::{ }; use std::cell::RefCell; +use std::ops::DerefMut; use std::sync::Arc; -pub use crate::style::text_editor::{Appearance, StyleSheet}; +pub use crate::style::text_editor::{Appearance, Highlight, StyleSheet}; pub use text::editor::{Action, Motion}; -pub struct TextEditor<'a, Message, Renderer = crate::Renderer> +pub struct TextEditor<'a, Highlighter, Message, Renderer = crate::Renderer> where + Highlighter: text::Highlighter, Renderer: text::Renderer, Renderer::Theme: StyleSheet, { @@ -31,9 +34,11 @@ where padding: Padding, style: ::Style, on_edit: Option Message + 'a>>, + highlighter_settings: Highlighter::Settings, } -impl<'a, Message, Renderer> TextEditor<'a, Message, Renderer> +impl<'a, Message, Renderer> + TextEditor<'a, highlighter::PlainText, Message, Renderer> where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -49,9 +54,19 @@ where padding: Padding::new(5.0), style: Default::default(), on_edit: None, + highlighter_settings: (), } } +} +impl<'a, Highlighter, Message, Renderer> + TextEditor<'a, Highlighter, Message, Renderer> +where + Highlighter: text::Highlighter, + Highlighter::Highlight: Highlight, + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ pub fn on_edit(mut self, on_edit: impl Fn(Action) -> Message + 'a) -> Self { self.on_edit = Some(Box::new(on_edit)); self @@ -160,20 +175,23 @@ where } } -struct State { +struct State { is_focused: bool, last_click: Option, drag_click: Option, + highlighter: RefCell, } -impl<'a, Message, Renderer> Widget - for TextEditor<'a, Message, Renderer> +impl<'a, Highlighter, Message, Renderer> Widget + for TextEditor<'a, Highlighter, Message, Renderer> where + Highlighter: text::Highlighter, + Highlighter::Highlight: Highlight, Renderer: text::Renderer, Renderer::Theme: StyleSheet, { fn tag(&self) -> widget::tree::Tag { - widget::tree::Tag::of::() + widget::tree::Tag::of::>() } fn state(&self) -> widget::tree::State { @@ -181,6 +199,9 @@ where is_focused: false, last_click: None, drag_click: None, + highlighter: RefCell::new(Highlighter::new( + &self.highlighter_settings, + )), }) } @@ -194,17 +215,19 @@ where fn layout( &self, - _tree: &mut widget::Tree, + tree: &mut widget::Tree, renderer: &Renderer, limits: &layout::Limits, ) -> iced_renderer::core::layout::Node { let mut internal = self.content.0.borrow_mut(); + let state = tree.state.downcast_mut::>(); internal.editor.update( limits.pad(self.padding).max(), self.font.unwrap_or_else(|| renderer.default_font()), self.text_size.unwrap_or_else(|| renderer.default_size()), self.line_height, + state.highlighter.borrow_mut().deref_mut(), ); layout::Node::new(limits.max()) @@ -225,7 +248,7 @@ where return event::Status::Ignored; }; - let state = tree.state.downcast_mut::(); + let state = tree.state.downcast_mut::>(); let Some(update) = Update::from_event( event, @@ -290,8 +313,14 @@ where ) { let bounds = layout.bounds(); - let internal = self.content.0.borrow(); - let state = tree.state.downcast_ref::(); + let mut internal = self.content.0.borrow_mut(); + let state = tree.state.downcast_ref::>(); + + internal.editor.highlight( + self.font.unwrap_or_else(|| renderer.default_font()), + state.highlighter.borrow_mut().deref_mut(), + |highlight| highlight.format(theme), + ); let is_disabled = self.on_edit.is_none(); let is_mouse_over = cursor.is_over(bounds); @@ -389,14 +418,19 @@ where } } -impl<'a, Message, Renderer> From> +impl<'a, Highlighter, Message, Renderer> + From> for Element<'a, Message, Renderer> where + Highlighter: text::Highlighter, + Highlighter::Highlight: Highlight, Message: 'a, Renderer: text::Renderer, Renderer::Theme: StyleSheet, { - fn from(text_editor: TextEditor<'a, Message, Renderer>) -> Self { + fn from( + text_editor: TextEditor<'a, Highlighter, Message, Renderer>, + ) -> Self { Self::new(text_editor) } } @@ -411,9 +445,9 @@ enum Update { } impl Update { - fn from_event( + fn from_event( event: Event, - state: &State, + state: &State, bounds: Rectangle, padding: Padding, cursor: mouse::Cursor, -- cgit From d3011992a76e83e12f74402c2ade616cdc7f1497 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 17 Sep 2023 19:03:58 +0200 Subject: Implement basic syntax highlighting with `syntect` in `editor` example --- core/src/text/highlighter.rs | 2 +- examples/editor/Cargo.toml | 4 +- examples/editor/src/main.rs | 168 ++++++++++++++++++++++++++++++++++++++++++- graphics/src/text/editor.rs | 25 ++++++- widget/src/text_editor.rs | 18 +++++ 5 files changed, 211 insertions(+), 6 deletions(-) diff --git a/core/src/text/highlighter.rs b/core/src/text/highlighter.rs index 1f9ac840..a929826f 100644 --- a/core/src/text/highlighter.rs +++ b/core/src/text/highlighter.rs @@ -3,7 +3,7 @@ use crate::Color; use std::hash::Hash; use std::ops::Range; -pub trait Highlighter: Clone + 'static { +pub trait Highlighter: 'static { type Settings: Hash; type Highlight; diff --git a/examples/editor/Cargo.toml b/examples/editor/Cargo.toml index 528cf23c..930ee592 100644 --- a/examples/editor/Cargo.toml +++ b/examples/editor/Cargo.toml @@ -7,4 +7,6 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["debug"] \ No newline at end of file +iced.features = ["advanced", "debug"] + +syntect = "5.1" \ No newline at end of file diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index 11819c69..a72feebc 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -1,6 +1,8 @@ use iced::widget::{container, text_editor}; use iced::{Element, Font, Sandbox, Settings, Theme}; +use highlighter::Highlighter; + pub fn main() -> iced::Result { Editor::run(Settings::default()) } @@ -41,7 +43,10 @@ impl Sandbox for Editor { container( text_editor(&self.content) .on_edit(Message::Edit) - .font(Font::with_name("Hasklug Nerd Font Mono")), + .font(Font::with_name("Hasklug Nerd Font Mono")) + .highlight::(highlighter::Settings { + token: String::from("md"), + }), ) .padding(20) .into() @@ -51,3 +56,164 @@ impl Sandbox for Editor { Theme::Dark } } + +mod highlighter { + use iced::advanced::text::highlighter; + use iced::widget::text_editor; + use iced::{Color, Font, Theme}; + + use std::ops::Range; + use syntect::highlighting; + use syntect::parsing; + + #[derive(Debug, Clone, Hash)] + pub struct Settings { + pub token: String, + } + + pub struct Highlight(highlighting::StyleModifier); + + impl text_editor::Highlight for Highlight { + fn format(&self, _theme: &Theme) -> highlighter::Format { + highlighter::Format { + color: self.0.foreground.map(|color| { + Color::from_rgba8( + color.r, + color.g, + color.b, + color.a as f32 / 255.0, + ) + }), + font: None, + } + } + } + + pub struct Highlighter { + syntaxes: parsing::SyntaxSet, + parser: parsing::ParseState, + stack: parsing::ScopeStack, + theme: highlighting::Theme, + token: String, + current_line: usize, + } + + impl highlighter::Highlighter for Highlighter { + type Settings = Settings; + type Highlight = Highlight; + + type Iterator<'a> = + Box, Self::Highlight)> + 'a>; + + fn new(settings: &Self::Settings) -> Self { + let syntaxes = parsing::SyntaxSet::load_defaults_nonewlines(); + + let syntax = syntaxes + .find_syntax_by_token(&settings.token) + .unwrap_or_else(|| syntaxes.find_syntax_plain_text()); + + let parser = parsing::ParseState::new(&syntax); + let stack = parsing::ScopeStack::new(); + + let theme = highlighting::ThemeSet::load_defaults() + .themes + .remove("base16-mocha.dark") + .unwrap(); + + Highlighter { + syntaxes, + parser, + stack, + theme, + token: settings.token.clone(), + current_line: 0, + } + } + + fn change_line(&mut self, _line: usize) { + // TODO: Caching + let syntax = self + .syntaxes + .find_syntax_by_token(&self.token) + .unwrap_or_else(|| self.syntaxes.find_syntax_plain_text()); + + self.parser = parsing::ParseState::new(&syntax); + self.stack = parsing::ScopeStack::new(); + self.current_line = 0; + } + + fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_> { + self.current_line += 1; + + let ops = self + .parser + .parse_line(line, &self.syntaxes) + .unwrap_or_default(); + + Box::new( + ScopeRangeIterator { + ops, + line_length: line.len(), + index: 0, + last_str_index: 0, + } + .filter_map(move |(range, scope)| { + let highlighter = + highlighting::Highlighter::new(&self.theme); + let _ = self.stack.apply(&scope); + + if range.is_empty() { + None + } else { + Some(( + range, + Highlight( + highlighter + .style_mod_for_stack(&self.stack.scopes), + ), + )) + } + }), + ) + } + + fn current_line(&self) -> usize { + self.current_line + } + } + + pub struct ScopeRangeIterator { + ops: Vec<(usize, parsing::ScopeStackOp)>, + line_length: usize, + index: usize, + last_str_index: usize, + } + + impl Iterator for ScopeRangeIterator { + type Item = (std::ops::Range, parsing::ScopeStackOp); + + fn next(&mut self) -> Option { + if self.index > self.ops.len() { + return None; + } + + let next_str_i = if self.index == self.ops.len() { + self.line_length + } else { + self.ops[self.index].0 + }; + + let range = self.last_str_index..next_str_i; + self.last_str_index = next_str_i; + + let op = if self.index == 0 { + parsing::ScopeStackOp::Noop + } else { + self.ops[self.index - 1].1.clone() + }; + + self.index += 1; + Some((range, op)) + } + } +} diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 901b4295..58fcc3dc 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -447,17 +447,26 @@ impl editor::Editor for Editor { text::font_system().write().expect("Write font system"); if font_system.version() != internal.version { + log::trace!("Updating `FontSystem` of `Editor`..."); + for line in internal.editor.buffer_mut().lines.iter_mut() { line.reset(); } } if new_font != internal.font { + log::trace!("Updating font of `Editor`..."); + for line in internal.editor.buffer_mut().lines.iter_mut() { let _ = line.set_attrs_list(cosmic_text::AttrsList::new( text::to_attributes(new_font), )); } + + internal.font = new_font; + internal.topmost_line_changed = Some(0); + + internal.editor.shape_as_needed(font_system.raw()); } let metrics = internal.editor.buffer().metrics(); @@ -466,6 +475,8 @@ impl editor::Editor for Editor { if new_size.0 != metrics.font_size || new_line_height.0 != metrics.line_height { + log::trace!("Updating `Metrics` of `Editor`..."); + internal.editor.buffer_mut().set_metrics( font_system.raw(), cosmic_text::Metrics::new(new_size.0, new_line_height.0), @@ -473,6 +484,8 @@ impl editor::Editor for Editor { } if new_bounds != internal.bounds { + log::trace!("Updating size of `Editor`..."); + internal.editor.buffer_mut().set_size( font_system.raw(), new_bounds.width, @@ -484,6 +497,10 @@ impl editor::Editor for Editor { if let Some(topmost_line_changed) = internal.topmost_line_changed.take() { + log::trace!( + "Notifying highlighter of line change: {topmost_line_changed}" + ); + new_highlighter.change_line(topmost_line_changed); } @@ -497,10 +514,12 @@ impl editor::Editor for Editor { format_highlight: impl Fn(&H::Highlight) -> highlighter::Format, ) { let internal = self.internal(); + let buffer = internal.editor.buffer(); - let scroll = internal.editor.buffer().scroll(); - let visible_lines = internal.editor.buffer().visible_lines(); - let last_visible_line = (scroll + visible_lines - 1) as usize; + let scroll = buffer.scroll(); + let visible_lines = buffer.visible_lines(); + let last_visible_line = + ((scroll + visible_lines) as usize).min(buffer.lines.len()) - 1; let current_line = highlighter.current_line(); diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index b17e1156..03adbb59 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -81,6 +81,24 @@ where self.padding = padding.into(); self } + + pub fn highlight( + self, + settings: H::Settings, + ) -> TextEditor<'a, H, Message, Renderer> { + TextEditor { + content: self.content, + font: self.font, + text_size: self.text_size, + line_height: self.line_height, + width: self.width, + height: self.height, + padding: self.padding, + style: self.style, + on_edit: self.on_edit, + highlighter_settings: settings, + } + } } pub struct Content(RefCell>) -- cgit From 23d00445ff1225b3e5ca99cb27966143cda8a2ce Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 17 Sep 2023 19:06:20 +0200 Subject: Use `saturating_sub` for `last_visible_line` in `text::Editor` --- graphics/src/text/editor.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 58fcc3dc..fbae287e 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -518,8 +518,9 @@ impl editor::Editor for Editor { let scroll = buffer.scroll(); let visible_lines = buffer.visible_lines(); - let last_visible_line = - ((scroll + visible_lines) as usize).min(buffer.lines.len()) - 1; + let last_visible_line = ((scroll + visible_lines) as usize) + .min(buffer.lines.len()) + .saturating_sub(1); let current_line = highlighter.current_line(); -- cgit From 2897986f2ded7318894a52572bec3d62754ebfaa Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 17 Sep 2023 19:27:51 +0200 Subject: Notify `Highlighter` of topmost line change --- core/src/text/editor.rs | 9 +++++-- graphics/src/text/editor.rs | 58 +++++++++++++++++++++++++++++++-------------- widget/src/text_editor.rs | 25 ++++++++++--------- 3 files changed, 61 insertions(+), 31 deletions(-) diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs index 0f439c8d..2144715f 100644 --- a/core/src/text/editor.rs +++ b/core/src/text/editor.rs @@ -47,13 +47,18 @@ pub enum Action { Select(Motion), SelectWord, SelectLine, + Edit(Edit), + Click(Point), + Drag(Point), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Edit { Insert(char), Paste(Arc), Enter, Backspace, Delete, - Click(Point), - Drag(Point), } #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index fbae287e..47c210bd 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -1,10 +1,12 @@ -use crate::core::text::editor::{self, Action, Cursor, Direction, Motion}; +use crate::core::text::editor::{ + self, Action, Cursor, Direction, Edit, Motion, +}; use crate::core::text::highlighter::{self, Highlighter}; use crate::core::text::LineHeight; use crate::core::{Font, Pixels, Point, Rectangle, Size}; use crate::text; -use cosmic_text::Edit; +use cosmic_text::Edit as _; use std::fmt; use std::sync::{self, Arc}; @@ -370,22 +372,42 @@ impl editor::Editor for Editor { } // Editing events - Action::Insert(c) => { - editor - .action(font_system.raw(), cosmic_text::Action::Insert(c)); - } - Action::Paste(text) => { - editor.insert_string(&text, None); - } - Action::Enter => { - editor.action(font_system.raw(), cosmic_text::Action::Enter); - } - Action::Backspace => { - editor - .action(font_system.raw(), cosmic_text::Action::Backspace); - } - Action::Delete => { - editor.action(font_system.raw(), cosmic_text::Action::Delete); + Action::Edit(edit) => { + match edit { + Edit::Insert(c) => { + editor.action( + font_system.raw(), + cosmic_text::Action::Insert(c), + ); + } + Edit::Paste(text) => { + editor.insert_string(&text, None); + } + Edit::Enter => { + editor.action( + font_system.raw(), + cosmic_text::Action::Enter, + ); + } + Edit::Backspace => { + editor.action( + font_system.raw(), + cosmic_text::Action::Backspace, + ); + } + Edit::Delete => { + editor.action( + font_system.raw(), + cosmic_text::Action::Delete, + ); + } + } + + let cursor = editor.cursor(); + let selection = editor.select_opt().unwrap_or(cursor); + + internal.topmost_line_changed = + Some(cursor.min(selection).line); } // Mouse events diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 03adbb59..c30e185f 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -17,7 +17,7 @@ use std::ops::DerefMut; use std::sync::Arc; pub use crate::style::text_editor::{Appearance, Highlight, StyleSheet}; -pub use text::editor::{Action, Motion}; +pub use text::editor::{Action, Edit, Motion}; pub struct TextEditor<'a, Highlighter, Message, Renderer = crate::Renderer> where @@ -301,7 +301,7 @@ where Update::Release => { state.drag_click = None; } - Update::Edit(action) => { + Update::Action(action) => { shell.publish(on_edit(action)); } Update::Copy => { @@ -311,7 +311,9 @@ where } Update::Paste => { if let Some(contents) = clipboard.read() { - shell.publish(on_edit(Action::Paste(Arc::new(contents)))); + shell.publish(on_edit(Action::Edit(Edit::Paste( + Arc::new(contents), + )))); } } } @@ -457,7 +459,7 @@ enum Update { Click(mouse::Click), Unfocus, Release, - Edit(Action), + Action(Action), Copy, Paste, } @@ -470,7 +472,8 @@ impl Update { padding: Padding, cursor: mouse::Cursor, ) -> Option { - let edit = |action| Some(Update::Edit(action)); + let action = |action| Some(Update::Action(action)); + let edit = |edit| action(Action::Edit(edit)); match event { Event::Mouse(event) => match event { @@ -499,7 +502,7 @@ impl Update { let cursor_position = cursor.position_in(bounds)? - Vector::new(padding.top, padding.left); - edit(Action::Drag(cursor_position)) + action(Action::Drag(cursor_position)) } _ => None, }, @@ -518,7 +521,7 @@ impl Update { motion }; - return edit(if modifiers.shift() { + return action(if modifiers.shift() { Action::Select(motion) } else { Action::Move(motion) @@ -526,9 +529,9 @@ impl Update { } match key_code { - keyboard::KeyCode::Enter => edit(Action::Enter), - keyboard::KeyCode::Backspace => edit(Action::Backspace), - keyboard::KeyCode::Delete => edit(Action::Delete), + keyboard::KeyCode::Enter => edit(Edit::Enter), + keyboard::KeyCode::Backspace => edit(Edit::Backspace), + keyboard::KeyCode::Delete => edit(Edit::Delete), keyboard::KeyCode::Escape => Some(Self::Unfocus), keyboard::KeyCode::C if modifiers.command() => { Some(Self::Copy) @@ -542,7 +545,7 @@ impl Update { } } keyboard::Event::CharacterReceived(c) if state.is_focused => { - edit(Action::Insert(c)) + edit(Edit::Insert(c)) } _ => None, }, -- cgit From 790c0dabcf0a50a2466e47daeb4f1e149b2ede5a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 17 Sep 2023 21:45:13 +0200 Subject: Implement syntax highlighting cache in `editor` example --- Cargo.toml | 4 +++ examples/editor/src/main.rs | 67 ++++++++++++++++++++++++++++----------------- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f8dd5f14..70f84460 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -155,3 +155,7 @@ winit = { git = "https://github.com/iced-rs/winit.git", rev = "c52db2045d0a2f1b8 [patch.crates-io.cosmic-text] git = "https://github.com/hecrj/cosmic-text.git" rev = "cb83458e7d0b84ef37c5beb72dda5046d7d343a6" + +[patch.crates-io.rangemap] +git = "https://github.com/hecrj/rangemap.git" +branch = "fix/partial-eq" diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index a72feebc..1235d38b 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -64,7 +64,7 @@ mod highlighter { use std::ops::Range; use syntect::highlighting; - use syntect::parsing; + use syntect::parsing::{self, SyntaxReference}; #[derive(Debug, Clone, Hash)] pub struct Settings { @@ -91,13 +91,14 @@ mod highlighter { pub struct Highlighter { syntaxes: parsing::SyntaxSet, - parser: parsing::ParseState, - stack: parsing::ScopeStack, + syntax: SyntaxReference, + caches: Vec<(parsing::ParseState, parsing::ScopeStack)>, theme: highlighting::Theme, - token: String, current_line: usize, } + const LINES_PER_SNAPSHOT: usize = 50; + impl highlighter::Highlighter for Highlighter { type Settings = Settings; type Highlight = Highlight; @@ -121,34 +122,53 @@ mod highlighter { .unwrap(); Highlighter { + syntax: syntax.clone(), syntaxes, - parser, - stack, + caches: vec![(parser, stack)], theme, - token: settings.token.clone(), current_line: 0, } } - fn change_line(&mut self, _line: usize) { - // TODO: Caching - let syntax = self - .syntaxes - .find_syntax_by_token(&self.token) - .unwrap_or_else(|| self.syntaxes.find_syntax_plain_text()); + fn change_line(&mut self, line: usize) { + let snapshot = line / LINES_PER_SNAPSHOT; + + if snapshot <= self.caches.len() { + self.caches.truncate(snapshot); + self.current_line = snapshot * LINES_PER_SNAPSHOT; + } else { + self.caches.truncate(1); + self.current_line = 0; + } + + let (parser, stack) = + self.caches.last().cloned().unwrap_or_else(|| { + ( + parsing::ParseState::new(&self.syntax), + parsing::ScopeStack::new(), + ) + }); - self.parser = parsing::ParseState::new(&syntax); - self.stack = parsing::ScopeStack::new(); - self.current_line = 0; + self.caches.push((parser, stack)); } fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_> { + if self.current_line / LINES_PER_SNAPSHOT >= self.caches.len() { + let (parser, stack) = + self.caches.last().expect("Caches must not be empty"); + + self.caches.push((parser.clone(), stack.clone())); + } + self.current_line += 1; - let ops = self - .parser - .parse_line(line, &self.syntaxes) - .unwrap_or_default(); + let (parser, stack) = + self.caches.last_mut().expect("Caches must not be empty"); + + let ops = + parser.parse_line(line, &self.syntaxes).unwrap_or_default(); + + let highlighter = highlighting::Highlighter::new(&self.theme); Box::new( ScopeRangeIterator { @@ -158,9 +178,7 @@ mod highlighter { last_str_index: 0, } .filter_map(move |(range, scope)| { - let highlighter = - highlighting::Highlighter::new(&self.theme); - let _ = self.stack.apply(&scope); + let _ = stack.apply(&scope); if range.is_empty() { None @@ -168,8 +186,7 @@ mod highlighter { Some(( range, Highlight( - highlighter - .style_mod_for_stack(&self.stack.scopes), + highlighter.style_mod_for_stack(&stack.scopes), ), )) } -- cgit From 86d396cf8bede8155bdd4a7d3f115a0108c67297 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 17 Sep 2023 23:15:38 +0200 Subject: Avoid adding unnecessary spans when syntax highlighting --- Cargo.toml | 2 +- graphics/src/text/editor.rs | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 70f84460..ac59085d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -154,7 +154,7 @@ winit = { git = "https://github.com/iced-rs/winit.git", rev = "c52db2045d0a2f1b8 [patch.crates-io.cosmic-text] git = "https://github.com/hecrj/cosmic-text.git" -rev = "cb83458e7d0b84ef37c5beb72dda5046d7d343a6" +branch = "editor-fixes" [patch.crates-io.rangemap] git = "https://github.com/hecrj/rangemap.git" diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 47c210bd..95061c3c 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -569,17 +569,19 @@ impl editor::Editor for Editor { for (range, highlight) in highlighter.highlight_line(line.text()) { let format = format_highlight(&highlight); - list.add_span( - range, - cosmic_text::Attrs { - color_opt: format.color.map(text::to_color), - ..if let Some(font) = format.font { - text::to_attributes(font) - } else { - attributes - } - }, - ); + if format.color.is_some() || format.font.is_some() { + list.add_span( + range, + cosmic_text::Attrs { + color_opt: format.color.map(text::to_color), + ..if let Some(font) = format.font { + text::to_attributes(font) + } else { + attributes + } + }, + ); + } } let _ = line.set_attrs_list(list); -- cgit From 8f8528a4ccee049aba779fe86cda786a52afac30 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 17 Sep 2023 23:20:15 +0200 Subject: Fix unnecessary dereference in `editor` example --- examples/editor/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index 1235d38b..74649676 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -113,7 +113,7 @@ mod highlighter { .find_syntax_by_token(&settings.token) .unwrap_or_else(|| syntaxes.find_syntax_plain_text()); - let parser = parsing::ParseState::new(&syntax); + let parser = parsing::ParseState::new(syntax); let stack = parsing::ScopeStack::new(); let theme = highlighting::ThemeSet::load_defaults() -- cgit From d1440ceca6340d045e556eb05354c254881732f0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 18 Sep 2023 13:39:47 +0200 Subject: Find correct `last_visible_line` in `Editor::highlight` --- graphics/src/text/editor.rs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 95061c3c..18c9b572 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -538,11 +538,27 @@ impl editor::Editor for Editor { let internal = self.internal(); let buffer = internal.editor.buffer(); - let scroll = buffer.scroll(); - let visible_lines = buffer.visible_lines(); - let last_visible_line = ((scroll + visible_lines) as usize) - .min(buffer.lines.len()) - .saturating_sub(1); + let mut window = buffer.scroll() + buffer.visible_lines(); + + let last_visible_line = buffer + .lines + .iter() + .enumerate() + .find_map(|(i, line)| { + let visible_lines = line + .layout_opt() + .as_ref() + .expect("Line layout should be cached") + .len() as i32; + + if window > visible_lines { + window -= visible_lines; + None + } else { + Some(i) + } + }) + .unwrap_or(buffer.lines.len()); let current_line = highlighter.current_line(); -- cgit From a01b123cec1b57a9100d56f567fcfbf91967b12f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 18 Sep 2023 13:57:47 +0200 Subject: Shape as needed only in `update` during `layout` --- graphics/src/text/editor.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 18c9b572..59096e74 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -442,8 +442,6 @@ impl editor::Editor for Editor { } } - editor.shape_as_needed(font_system.raw()); - self.0 = Some(Arc::new(internal)); } @@ -487,8 +485,6 @@ impl editor::Editor for Editor { internal.font = new_font; internal.topmost_line_changed = Some(0); - - internal.editor.shape_as_needed(font_system.raw()); } let metrics = internal.editor.buffer().metrics(); @@ -526,6 +522,8 @@ impl editor::Editor for Editor { new_highlighter.change_line(topmost_line_changed); } + internal.editor.shape_as_needed(font_system.raw()); + self.0 = Some(Arc::new(internal)); } -- cgit From b5466f41ca33452fb0d4e8470856c027d3b26e39 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 18 Sep 2023 13:58:39 +0200 Subject: Fix inconsistent `expect` messages in `text::editor` --- graphics/src/text/editor.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 59096e74..de1b998b 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -43,7 +43,7 @@ impl Editor { fn internal(&self) -> &Arc { self.0 .as_ref() - .expect("editor should always be initialized") + .expect("Editor should always be initialized") } } @@ -458,7 +458,7 @@ impl editor::Editor for Editor { new_highlighter: &mut impl Highlighter, ) { let editor = - self.0.take().expect("editor should always be initialized"); + self.0.take().expect("Editor should always be initialized"); let mut internal = Arc::try_unwrap(editor) .expect("Editor cannot have multiple strong references"); @@ -565,7 +565,7 @@ impl editor::Editor for Editor { } let editor = - self.0.take().expect("editor should always be initialized"); + self.0.take().expect("Editor should always be initialized"); let mut internal = Arc::try_unwrap(editor) .expect("Editor cannot have multiple strong references"); -- cgit From 61ef8f3249218b301d434d04c483ba70562c1df4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 18 Sep 2023 13:58:55 +0200 Subject: Update `version` properly when `FontSystem` changes in `text::editor` --- graphics/src/text/editor.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index de1b998b..4673fce3 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -472,6 +472,9 @@ impl editor::Editor for Editor { for line in internal.editor.buffer_mut().lines.iter_mut() { line.reset(); } + + internal.version = font_system.version(); + internal.topmost_line_changed = Some(0); } if new_font != internal.font { -- cgit From 8446fe6de52fa68077d23d39f728f79a29b52f00 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 18 Sep 2023 14:38:54 +0200 Subject: Implement theme selector in `editor` example --- core/src/text/highlighter.rs | 7 ++- examples/editor/src/main.rs | 101 +++++++++++++++++++++++++++++++++++-------- widget/src/text_editor.rs | 13 +++++- 3 files changed, 99 insertions(+), 22 deletions(-) diff --git a/core/src/text/highlighter.rs b/core/src/text/highlighter.rs index a929826f..b462d083 100644 --- a/core/src/text/highlighter.rs +++ b/core/src/text/highlighter.rs @@ -1,10 +1,9 @@ use crate::Color; -use std::hash::Hash; use std::ops::Range; pub trait Highlighter: 'static { - type Settings: Hash; + type Settings: PartialEq + Clone; type Highlight; type Iterator<'a>: Iterator, Self::Highlight)> @@ -13,6 +12,8 @@ pub trait Highlighter: 'static { fn new(settings: &Self::Settings) -> Self; + fn update(&mut self, new_settings: &Self::Settings); + fn change_line(&mut self, line: usize); fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_>; @@ -38,6 +39,8 @@ impl Highlighter for PlainText { Self } + fn update(&mut self, _new_settings: &Self::Settings) {} + fn change_line(&mut self, _line: usize) {} fn highlight_line(&mut self, _line: &str) -> Self::Iterator<'_> { diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index 74649676..fa35ba0f 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -1,5 +1,5 @@ -use iced::widget::{container, text_editor}; -use iced::{Element, Font, Sandbox, Settings, Theme}; +use iced::widget::{column, horizontal_space, pick_list, row, text_editor}; +use iced::{Element, Font, Length, Sandbox, Settings, Theme}; use highlighter::Highlighter; @@ -9,11 +9,13 @@ pub fn main() -> iced::Result { struct Editor { content: text_editor::Content, + theme: highlighter::Theme, } #[derive(Debug, Clone)] enum Message { Edit(text_editor::Action), + ThemeSelected(highlighter::Theme), } impl Sandbox for Editor { @@ -21,9 +23,8 @@ impl Sandbox for Editor { fn new() -> Self { Self { - content: text_editor::Content::with(include_str!( - "../../../README.md" - )), + content: text_editor::Content::with(include_str!("main.rs")), + theme: highlighter::Theme::SolarizedDark, } } @@ -36,18 +37,33 @@ impl Sandbox for Editor { Message::Edit(action) => { self.content.edit(action); } + Message::ThemeSelected(theme) => { + self.theme = theme; + } } } fn view(&self) -> Element { - container( + column![ + row![ + horizontal_space(Length::Fill), + pick_list( + highlighter::Theme::ALL, + Some(self.theme), + Message::ThemeSelected + ) + .padding([5, 10]) + ] + .spacing(10), text_editor(&self.content) .on_edit(Message::Edit) .font(Font::with_name("Hasklug Nerd Font Mono")) .highlight::(highlighter::Settings { - token: String::from("md"), + theme: self.theme, + extension: String::from("rs"), }), - ) + ] + .spacing(10) .padding(20) .into() } @@ -60,21 +76,52 @@ impl Sandbox for Editor { mod highlighter { use iced::advanced::text::highlighter; use iced::widget::text_editor; - use iced::{Color, Font, Theme}; + use iced::{Color, Font}; use std::ops::Range; use syntect::highlighting; use syntect::parsing::{self, SyntaxReference}; - #[derive(Debug, Clone, Hash)] + #[derive(Debug, Clone, PartialEq)] pub struct Settings { - pub token: String, + pub theme: Theme, + pub extension: String, + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum Theme { + SolarizedDark, + InspiredGitHub, + Base16Mocha, + } + + impl Theme { + pub const ALL: &[Self] = + &[Self::SolarizedDark, Self::InspiredGitHub, Self::Base16Mocha]; + + fn key(&self) -> &'static str { + match self { + Theme::InspiredGitHub => "InspiredGitHub", + Theme::Base16Mocha => "base16-mocha.dark", + Theme::SolarizedDark => "Solarized (dark)", + } + } + } + + impl std::fmt::Display for Theme { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Theme::InspiredGitHub => write!(f, "Inspired GitHub"), + Theme::Base16Mocha => write!(f, "Mocha"), + Theme::SolarizedDark => write!(f, "Solarized Dark"), + } + } } pub struct Highlight(highlighting::StyleModifier); impl text_editor::Highlight for Highlight { - fn format(&self, _theme: &Theme) -> highlighter::Format { + fn format(&self, _theme: &iced::Theme) -> highlighter::Format { highlighter::Format { color: self.0.foreground.map(|color| { Color::from_rgba8( @@ -92,8 +139,8 @@ mod highlighter { pub struct Highlighter { syntaxes: parsing::SyntaxSet, syntax: SyntaxReference, - caches: Vec<(parsing::ParseState, parsing::ScopeStack)>, theme: highlighting::Theme, + caches: Vec<(parsing::ParseState, parsing::ScopeStack)>, current_line: usize, } @@ -110,26 +157,42 @@ mod highlighter { let syntaxes = parsing::SyntaxSet::load_defaults_nonewlines(); let syntax = syntaxes - .find_syntax_by_token(&settings.token) + .find_syntax_by_token(&settings.extension) .unwrap_or_else(|| syntaxes.find_syntax_plain_text()); - let parser = parsing::ParseState::new(syntax); - let stack = parsing::ScopeStack::new(); - let theme = highlighting::ThemeSet::load_defaults() .themes - .remove("base16-mocha.dark") + .remove(settings.theme.key()) .unwrap(); + let parser = parsing::ParseState::new(syntax); + let stack = parsing::ScopeStack::new(); + Highlighter { syntax: syntax.clone(), syntaxes, - caches: vec![(parser, stack)], theme, + caches: vec![(parser, stack)], current_line: 0, } } + fn update(&mut self, new_settings: &Self::Settings) { + self.syntax = self + .syntaxes + .find_syntax_by_token(&new_settings.extension) + .unwrap_or_else(|| self.syntaxes.find_syntax_plain_text()) + .clone(); + + self.theme = highlighting::ThemeSet::load_defaults() + .themes + .remove(new_settings.theme.key()) + .unwrap(); + + // Restart the highlighter + self.change_line(0); + } + fn change_line(&mut self, line: usize) { let snapshot = line / LINES_PER_SNAPSHOT; diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index c30e185f..0cde2c98 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -193,11 +193,12 @@ where } } -struct State { +struct State { is_focused: bool, last_click: Option, drag_click: Option, highlighter: RefCell, + highlighter_settings: Highlighter::Settings, } impl<'a, Highlighter, Message, Renderer> Widget @@ -220,6 +221,7 @@ where highlighter: RefCell::new(Highlighter::new( &self.highlighter_settings, )), + highlighter_settings: self.highlighter_settings.clone(), }) } @@ -240,6 +242,15 @@ where let mut internal = self.content.0.borrow_mut(); let state = tree.state.downcast_mut::>(); + if state.highlighter_settings != self.highlighter_settings { + state + .highlighter + .borrow_mut() + .update(&self.highlighter_settings); + + state.highlighter_settings = self.highlighter_settings.clone(); + } + internal.editor.update( limits.pad(self.padding).max(), self.font.unwrap_or_else(|| renderer.default_font()), -- cgit From e7326f0af6f16cf2ff04fbac93bf296a044923f4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 18 Sep 2023 19:07:41 +0200 Subject: Flesh out the `editor` example a bit more --- core/src/renderer/null.rs | 4 + core/src/text/editor.rs | 8 ++ examples/editor/Cargo.toml | 8 +- examples/editor/fonts/icons.ttf | Bin 0 -> 6352 bytes examples/editor/src/main.rs | 287 ++++++++++++++++++++++++++++++++++++---- graphics/src/text/editor.rs | 8 +- src/settings.rs | 8 ++ widget/src/text_editor.rs | 4 + winit/src/application.rs | 9 +- winit/src/settings.rs | 4 + 10 files changed, 312 insertions(+), 28 deletions(-) create mode 100644 examples/editor/fonts/icons.ttf diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index 21597c8e..da0f32de 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -125,6 +125,10 @@ impl text::Editor for () { text::editor::Cursor::Caret(Point::ORIGIN) } + fn cursor_position(&self) -> (usize, usize) { + (0, 0) + } + fn selection(&self) -> Option { None } diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs index 2144715f..13bafc3d 100644 --- a/core/src/text/editor.rs +++ b/core/src/text/editor.rs @@ -12,6 +12,8 @@ pub trait Editor: Sized + Default { fn cursor(&self) -> Cursor; + fn cursor_position(&self) -> (usize, usize); + fn selection(&self) -> Option; fn line(&self, index: usize) -> Option<&str>; @@ -52,6 +54,12 @@ pub enum Action { Drag(Point), } +impl Action { + pub fn is_edit(&self) -> bool { + matches!(self, Self::Edit(_)) + } +} + #[derive(Debug, Clone, PartialEq)] pub enum Edit { Insert(char), diff --git a/examples/editor/Cargo.toml b/examples/editor/Cargo.toml index 930ee592..eeb34aa1 100644 --- a/examples/editor/Cargo.toml +++ b/examples/editor/Cargo.toml @@ -7,6 +7,10 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["advanced", "debug"] +iced.features = ["advanced", "tokio", "debug"] -syntect = "5.1" \ No newline at end of file +tokio.workspace = true +tokio.features = ["fs"] + +syntect = "5.1" +rfd = "0.12" diff --git a/examples/editor/fonts/icons.ttf b/examples/editor/fonts/icons.ttf new file mode 100644 index 00000000..393c6922 Binary files /dev/null and b/examples/editor/fonts/icons.ttf differ diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index fa35ba0f..09c4b9b5 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -1,70 +1,218 @@ -use iced::widget::{column, horizontal_space, pick_list, row, text_editor}; -use iced::{Element, Font, Length, Sandbox, Settings, Theme}; +use iced::executor; +use iced::theme::{self, Theme}; +use iced::widget::{ + button, column, container, horizontal_space, pick_list, row, text, + text_editor, tooltip, +}; +use iced::{Application, Command, Element, Font, Length, Settings}; use highlighter::Highlighter; +use std::ffi; +use std::io; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + pub fn main() -> iced::Result { - Editor::run(Settings::default()) + Editor::run(Settings { + fonts: vec![include_bytes!("../fonts/icons.ttf").as_slice().into()], + default_font: Font { + monospaced: true, + ..Font::with_name("Hasklug Nerd Font Mono") + }, + ..Settings::default() + }) } struct Editor { + file: Option, content: text_editor::Content, theme: highlighter::Theme, + is_loading: bool, + is_dirty: bool, } #[derive(Debug, Clone)] enum Message { Edit(text_editor::Action), ThemeSelected(highlighter::Theme), + NewFile, + OpenFile, + FileOpened(Result<(PathBuf, Arc), Error>), + SaveFile, + FileSaved(Result), } -impl Sandbox for Editor { +impl Application for Editor { type Message = Message; - - fn new() -> Self { - Self { - content: text_editor::Content::with(include_str!("main.rs")), - theme: highlighter::Theme::SolarizedDark, - } + type Theme = Theme; + type Executor = executor::Default; + type Flags = (); + + fn new(_flags: Self::Flags) -> (Self, Command) { + ( + Self { + file: None, + content: text_editor::Content::new(), + theme: highlighter::Theme::SolarizedDark, + is_loading: true, + is_dirty: false, + }, + Command::perform(load_file(default_file()), Message::FileOpened), + ) } fn title(&self) -> String { String::from("Editor - Iced") } - fn update(&mut self, message: Message) { + fn update(&mut self, message: Message) -> Command { match message { Message::Edit(action) => { + self.is_dirty = self.is_dirty || action.is_edit(); + self.content.edit(action); + + Command::none() } Message::ThemeSelected(theme) => { self.theme = theme; + + Command::none() + } + Message::NewFile => { + if !self.is_loading { + self.file = None; + self.content = text_editor::Content::new(); + } + + Command::none() + } + Message::OpenFile => { + if self.is_loading { + Command::none() + } else { + self.is_loading = true; + + Command::perform(open_file(), Message::FileOpened) + } + } + Message::FileOpened(result) => { + self.is_loading = false; + self.is_dirty = false; + + if let Ok((path, contents)) = result { + self.file = Some(path); + self.content = text_editor::Content::with(&contents); + } + + Command::none() + } + Message::SaveFile => { + if self.is_loading { + Command::none() + } else { + self.is_loading = true; + + let mut contents = self.content.lines().enumerate().fold( + String::new(), + |mut contents, (i, line)| { + if i > 0 { + contents.push_str("\n"); + } + + contents.push_str(&line); + + contents + }, + ); + + if !contents.ends_with("\n") { + contents.push_str("\n"); + } + + Command::perform( + save_file(self.file.clone(), contents), + Message::FileSaved, + ) + } + } + Message::FileSaved(result) => { + self.is_loading = false; + + if let Ok(path) = result { + self.file = Some(path); + self.is_dirty = false; + } + + Command::none() } } } fn view(&self) -> Element { + let controls = row![ + action(new_icon(), "New file", Some(Message::NewFile)), + action( + open_icon(), + "Open file", + (!self.is_loading).then_some(Message::OpenFile) + ), + action( + save_icon(), + "Save file", + self.is_dirty.then_some(Message::SaveFile) + ), + horizontal_space(Length::Fill), + pick_list( + highlighter::Theme::ALL, + Some(self.theme), + Message::ThemeSelected + ) + .text_size(14) + .padding([5, 10]) + ] + .spacing(10); + + let status = row![ + text(if let Some(path) = &self.file { + let path = path.display().to_string(); + + if path.len() > 60 { + format!("...{}", &path[path.len() - 40..]) + } else { + path + } + } else { + String::from("New file") + }), + horizontal_space(Length::Fill), + text({ + let (line, column) = self.content.cursor_position(); + + format!("{}:{}", line + 1, column + 1) + }) + ] + .spacing(10); + column![ - row![ - horizontal_space(Length::Fill), - pick_list( - highlighter::Theme::ALL, - Some(self.theme), - Message::ThemeSelected - ) - .padding([5, 10]) - ] - .spacing(10), + controls, text_editor(&self.content) .on_edit(Message::Edit) - .font(Font::with_name("Hasklug Nerd Font Mono")) .highlight::(highlighter::Settings { theme: self.theme, - extension: String::from("rs"), + extension: self + .file + .as_deref() + .and_then(Path::extension) + .and_then(ffi::OsStr::to_str) + .map(str::to_string) + .unwrap_or(String::from("rs")), }), + status, ] .spacing(10) - .padding(20) + .padding(10) .into() } @@ -73,6 +221,97 @@ impl Sandbox for Editor { } } +#[derive(Debug, Clone)] +pub enum Error { + DialogClosed, + IoError(io::ErrorKind), +} + +fn default_file() -> PathBuf { + PathBuf::from(format!("{}/src/main.rs", env!("CARGO_MANIFEST_DIR"))) +} + +async fn open_file() -> Result<(PathBuf, Arc), Error> { + let picked_file = rfd::AsyncFileDialog::new() + .set_title("Open a text file...") + .pick_file() + .await + .ok_or(Error::DialogClosed)?; + + load_file(picked_file.path().to_owned()).await +} + +async fn load_file(path: PathBuf) -> Result<(PathBuf, Arc), Error> { + let contents = tokio::fs::read_to_string(&path) + .await + .map(Arc::new) + .map_err(|error| Error::IoError(error.kind()))?; + + Ok((path, contents)) +} + +async fn save_file( + path: Option, + contents: String, +) -> Result { + let path = if let Some(path) = path { + path + } else { + rfd::AsyncFileDialog::new() + .save_file() + .await + .as_ref() + .map(rfd::FileHandle::path) + .map(Path::to_owned) + .ok_or(Error::DialogClosed)? + }; + + let _ = tokio::fs::write(&path, contents) + .await + .map_err(|error| Error::IoError(error.kind()))?; + + Ok(path) +} + +fn action<'a, Message: Clone + 'a>( + content: impl Into>, + label: &'a str, + on_press: Option, +) -> Element<'a, Message> { + let action = + button(container(content).width(Length::Fill).center_x()).width(40); + + if let Some(on_press) = on_press { + tooltip( + action.on_press(on_press), + label, + tooltip::Position::FollowCursor, + ) + .style(theme::Container::Box) + .into() + } else { + action.style(theme::Button::Secondary).into() + } +} + +fn new_icon<'a, Message>() -> Element<'a, Message> { + icon('\u{0e800}') +} + +fn save_icon<'a, Message>() -> Element<'a, Message> { + icon('\u{0e801}') +} + +fn open_icon<'a, Message>() -> Element<'a, Message> { + icon('\u{0f115}') +} + +fn icon<'a, Message>(codepoint: char) -> Element<'a, Message> { + const ICON_FONT: Font = Font::with_name("editor-icons"); + + text(codepoint).font(ICON_FONT).into() +} + mod highlighter { use iced::advanced::text::highlighter; use iced::widget::text_editor; diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 4673fce3..dfb91f34 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -221,6 +221,12 @@ impl editor::Editor for Editor { } } + fn cursor_position(&self) -> (usize, usize) { + let cursor = self.internal().editor.cursor(); + + (cursor.line, cursor.index) + } + fn perform(&mut self, action: Action) { let mut font_system = text::font_system().write().expect("Write font system"); @@ -559,7 +565,7 @@ impl editor::Editor for Editor { Some(i) } }) - .unwrap_or(buffer.lines.len()); + .unwrap_or(buffer.lines.len().saturating_sub(1)); let current_line = highlighter.current_line(); diff --git a/src/settings.rs b/src/settings.rs index d9778d7e..6b9ce095 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -2,6 +2,8 @@ use crate::window; use crate::{Font, Pixels}; +use std::borrow::Cow; + /// The settings of an application. #[derive(Debug, Clone)] pub struct Settings { @@ -21,6 +23,9 @@ pub struct Settings { /// [`Application`]: crate::Application pub flags: Flags, + /// The fonts to load on boot. + pub fonts: Vec>, + /// The default [`Font`] to be used. /// /// By default, it uses [`Family::SansSerif`](crate::font::Family::SansSerif). @@ -62,6 +67,7 @@ impl Settings { flags, id: default_settings.id, window: default_settings.window, + fonts: default_settings.fonts, default_font: default_settings.default_font, default_text_size: default_settings.default_text_size, antialiasing: default_settings.antialiasing, @@ -79,6 +85,7 @@ where id: None, window: Default::default(), flags: Default::default(), + fonts: Default::default(), default_font: Default::default(), default_text_size: Pixels(16.0), antialiasing: false, @@ -93,6 +100,7 @@ impl From> for iced_winit::Settings { id: settings.id, window: settings.window.into(), flags: settings.flags, + fonts: settings.fonts, exit_on_close_request: settings.exit_on_close_request, } } diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 0cde2c98..970ec031 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -182,6 +182,10 @@ where pub fn selection(&self) -> Option { self.0.borrow().editor.selection() } + + pub fn cursor_position(&self) -> (usize, usize) { + self.0.borrow().editor.cursor_position() + } } impl Default for Content diff --git a/winit/src/application.rs b/winit/src/application.rs index d1689452..e80e9783 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -193,7 +193,14 @@ where }; } - let (compositor, renderer) = C::new(compositor_settings, Some(&window))?; + let (compositor, mut renderer) = + C::new(compositor_settings, Some(&window))?; + + for font in settings.fonts { + use crate::core::text::Renderer; + + renderer.load_font(font); + } let (mut event_sender, event_receiver) = mpsc::unbounded(); let (control_sender, mut control_receiver) = mpsc::unbounded(); diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 8d3e1b47..b4a1dd61 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -33,6 +33,7 @@ use crate::Position; use winit::monitor::MonitorHandle; use winit::window::WindowBuilder; +use std::borrow::Cow; use std::fmt; /// The settings of an application. @@ -52,6 +53,9 @@ pub struct Settings { /// [`Application`]: crate::Application pub flags: Flags, + /// The fonts to load on boot. + pub fonts: Vec>, + /// Whether the [`Application`] should exit when the user requests the /// window to close (e.g. the user presses the close button). /// -- cgit From 161a971d065b3254a2f11cb374d2c94c2d67646b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 18 Sep 2023 19:08:57 +0200 Subject: Fix `clippy` lints --- examples/editor/src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index 09c4b9b5..785dfb3b 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -118,7 +118,7 @@ impl Application for Editor { String::new(), |mut contents, (i, line)| { if i > 0 { - contents.push_str("\n"); + contents.push('\n'); } contents.push_str(&line); @@ -127,8 +127,8 @@ impl Application for Editor { }, ); - if !contents.ends_with("\n") { - contents.push_str("\n"); + if !contents.ends_with('\n') { + contents.push('\n'); } Command::perform( @@ -266,7 +266,7 @@ async fn save_file( .ok_or(Error::DialogClosed)? }; - let _ = tokio::fs::write(&path, contents) + tokio::fs::write(&path, contents) .await .map_err(|error| Error::IoError(error.kind()))?; -- cgit From 8eec0033dee816bfcc102fc4f511c8bfe08c14ee Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 18 Sep 2023 19:24:09 +0200 Subject: Remove unnecessary `monospaced` flag in `Font` --- core/src/font.rs | 4 ---- examples/editor/src/main.rs | 5 +---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/core/src/font.rs b/core/src/font.rs index 7f647847..2b68decf 100644 --- a/core/src/font.rs +++ b/core/src/font.rs @@ -12,8 +12,6 @@ pub struct Font { pub stretch: Stretch, /// The [`Style`] of the [`Font`]. pub style: Style, - /// Whether if the [`Font`] is monospaced or not. - pub monospaced: bool, } impl Font { @@ -23,13 +21,11 @@ impl Font { weight: Weight::Normal, stretch: Stretch::Normal, style: Style::Normal, - monospaced: false, }; /// A monospaced font with normal [`Weight`]. pub const MONOSPACE: Font = Font { family: Family::Monospace, - monospaced: true, ..Self::DEFAULT }; diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index 785dfb3b..5018b3cb 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -16,10 +16,7 @@ use std::sync::Arc; pub fn main() -> iced::Result { Editor::run(Settings { fonts: vec![include_bytes!("../fonts/icons.ttf").as_slice().into()], - default_font: Font { - monospaced: true, - ..Font::with_name("Hasklug Nerd Font Mono") - }, + default_font: Font::with_name("Hasklug Nerd Font Mono"), ..Settings::default() }) } -- cgit From d1d0b3aaee84003278b9db3e86687e776f20b346 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 18 Sep 2023 20:14:38 +0200 Subject: Use `Font::MONOSPACE` in `editor` example --- Cargo.toml | 2 +- examples/editor/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ac59085d..e887afc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -154,7 +154,7 @@ winit = { git = "https://github.com/iced-rs/winit.git", rev = "c52db2045d0a2f1b8 [patch.crates-io.cosmic-text] git = "https://github.com/hecrj/cosmic-text.git" -branch = "editor-fixes" +branch = "respect-fontconfig-aliases" [patch.crates-io.rangemap] git = "https://github.com/hecrj/rangemap.git" diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index 5018b3cb..277eb3e9 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -16,7 +16,7 @@ use std::sync::Arc; pub fn main() -> iced::Result { Editor::run(Settings { fonts: vec![include_bytes!("../fonts/icons.ttf").as_slice().into()], - default_font: Font::with_name("Hasklug Nerd Font Mono"), + default_font: Font::MONOSPACE, ..Settings::default() }) } -- cgit From 36e867de693d4e9fc64da3d9d7745a5b1398d8a5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 18 Sep 2023 20:59:39 +0200 Subject: Fix `lint` and `test` GitHub CI workflows --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6fd98374..af34bb13 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,7 +2,7 @@ name: Lint on: [push, pull_request] jobs: all: - runs-on: ubuntu-latest + runs-on: macOS-latest steps: - uses: hecrj/setup-rust-action@v1 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ac8d27f9..215b616b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: run: | export DEBIAN_FRONTED=noninteractive sudo apt-get -qq update - sudo apt-get install -y libxkbcommon-dev + sudo apt-get install -y libxkbcommon-dev libgtk-3-dev - name: Run tests run: | cargo test --verbose --workspace -- cgit From 4e757a26d0c1c58001f31cf0592131cd5ad886ad Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Sep 2023 01:18:06 +0200 Subject: Implement `Scroll` action in `text::editor` --- core/src/text/editor.rs | 1 + graphics/src/text/editor.rs | 6 ++++++ widget/src/text_editor.rs | 12 ++++++++++++ 3 files changed, 19 insertions(+) diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs index 13bafc3d..e9d66ce9 100644 --- a/core/src/text/editor.rs +++ b/core/src/text/editor.rs @@ -52,6 +52,7 @@ pub enum Action { Edit(Edit), Click(Point), Drag(Point), + Scroll { lines: i32 }, } impl Action { diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index dfb91f34..a05312dc 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -446,6 +446,12 @@ impl editor::Editor for Editor { } } } + Action::Scroll { lines } => { + editor.action( + font_system.raw(), + cosmic_text::Action::Scroll { lines }, + ); + } } self.0 = Some(Arc::new(internal)); diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 970ec031..ad12a076 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -521,6 +521,18 @@ impl Update { } _ => None, }, + mouse::Event::WheelScrolled { delta } => { + action(Action::Scroll { + lines: match delta { + mouse::ScrollDelta::Lines { y, .. } => { + -y as i32 * 4 + } + mouse::ScrollDelta::Pixels { y, .. } => { + -y.signum() as i32 + } + }, + }) + } _ => None, }, Event::Keyboard(event) => match event { -- cgit From 3d6b9637c3b1c9f3c654a3ecef7a247cfd6edef3 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Tue, 19 Sep 2023 01:31:10 -0400 Subject: Chore: Inline format args for ease of reading A minor cleanup to inline all simple cases of format arguments. Makes the format strings just a bit easier to read. --- examples/game_of_life/src/main.rs | 3 +-- examples/screenshot/src/main.rs | 10 +++++----- examples/system_information/src/main.rs | 4 ++-- examples/todos/src/main.rs | 3 +-- examples/visible_bounds/src/main.rs | 2 +- futures/src/subscription/tracker.rs | 3 +-- renderer/src/lib.rs | 2 +- tiny_skia/src/backend.rs | 6 ++---- wgpu/src/image/atlas.rs | 8 ++++---- wgpu/src/image/vector.rs | 2 +- wgpu/src/window/compositor.rs | 6 +++--- winit/src/application.rs | 4 ++-- winit/src/clipboard.rs | 2 +- 13 files changed, 25 insertions(+), 30 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index e451cb06..21a21142 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -610,8 +610,7 @@ mod grid { frame.fill_text(Text { content: format!( - "{} cell{} @ {:?} ({})", - cell_count, + "{cell_count} cell{} @ {:?} ({})", if cell_count == 1 { "" } else { "s" }, self.last_tick_duration, self.last_queued_ticks diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 68c9d031..1c9e187c 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -184,8 +184,8 @@ impl Application for Example { .align_items(Alignment::Center); if let Some(crop_error) = &self.crop_error { - crop_controls = crop_controls - .push(text(format!("Crop error! \n{}", crop_error))); + crop_controls = + crop_controls.push(text(format!("Crop error! \n{crop_error}"))); } let mut controls = column![ @@ -221,9 +221,9 @@ impl Application for Example { if let Some(png_result) = &self.saved_png_path { let msg = match png_result { - Ok(path) => format!("Png saved as: {:?}!", path), + Ok(path) => format!("Png saved as: {path:?}!"), Err(msg) => { - format!("Png could not be saved due to:\n{:?}", msg) + format!("Png could not be saved due to:\n{msg:?}") } }; @@ -281,7 +281,7 @@ async fn save_to_png(screenshot: Screenshot) -> Result { ColorType::Rgba8, ) .map(|_| path) - .map_err(|err| PngError(format!("{:?}", err))) + .map_err(|err| PngError(format!("{err:?}"))) } #[derive(Clone, Debug)] diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs index 633b6e2b..507431ee 100644 --- a/examples/system_information/src/main.rs +++ b/examples/system_information/src/main.rs @@ -105,8 +105,8 @@ impl Application for Example { ByteSize::kb(information.memory_total).to_string(); let memory_total = text(format!( - "Memory (total): {} kb ({})", - information.memory_total, memory_readable + "Memory (total): {} kb ({memory_readable})", + information.memory_total, )); let memory_text = if let Some(memory_used) = diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 501bf67e..3048a668 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -415,8 +415,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element { row![ text(format!( - "{} {} left", - tasks_left, + "{tasks_left} {} left", if tasks_left == 1 { "task" } else { "tasks" } )) .width(Length::Fill), diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs index 42dfc24c..697badb4 100644 --- a/examples/visible_bounds/src/main.rs +++ b/examples/visible_bounds/src/main.rs @@ -94,7 +94,7 @@ impl Application for Example { data_row( label, match bounds { - Some(bounds) => format!("{:?}", bounds), + Some(bounds) => format!("{bounds:?}"), None => "not visible".to_string(), }, if bounds diff --git a/futures/src/subscription/tracker.rs b/futures/src/subscription/tracker.rs index 3a83da09..15ed5b87 100644 --- a/futures/src/subscription/tracker.rs +++ b/futures/src/subscription/tracker.rs @@ -147,8 +147,7 @@ impl Tracker { .for_each(|listener| { if let Err(error) = listener.try_send((event.clone(), status)) { log::warn!( - "Error sending event to subscription: {:?}", - error + "Error sending event to subscription: {error:?}" ); } }); diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 8bdf231d..ef5c4182 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -60,7 +60,7 @@ impl Renderer { pub fn draw_mesh(&mut self, mesh: Mesh) { match self { Self::TinySkia(_) => { - log::warn!("Unsupported mesh primitive: {:?}", mesh) + log::warn!("Unsupported mesh primitive: {mesh:?}") } #[cfg(feature = "wgpu")] Self::Wgpu(renderer) => { diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index c721d96e..65aca4b0 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -443,8 +443,7 @@ impl Backend { #[cfg(not(feature = "image"))] Primitive::Image { .. } => { log::warn!( - "Unsupported primitive in `iced_tiny_skia`: {:?}", - primitive + "Unsupported primitive in `iced_tiny_skia`: {primitive:?}", ); } #[cfg(feature = "svg")] @@ -473,8 +472,7 @@ impl Backend { #[cfg(not(feature = "svg"))] Primitive::Svg { .. } => { log::warn!( - "Unsupported primitive in `iced_tiny_skia`: {:?}", - primitive + "Unsupported primitive in `iced_tiny_skia`: {primitive:?}", ); } Primitive::Custom(primitive::Custom::Fill { diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index e3de1290..1253496b 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -86,7 +86,7 @@ impl Atlas { entry }; - log::info!("Allocated atlas entry: {:?}", entry); + log::info!("Allocated atlas entry: {entry:?}"); // It is a webgpu requirement that: // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 @@ -139,13 +139,13 @@ impl Atlas { } } - log::info!("Current atlas: {:?}", self); + log::info!("Current atlas: {self:?}"); Some(entry) } pub fn remove(&mut self, entry: &Entry) { - log::info!("Removing atlas entry: {:?}", entry); + log::info!("Removing atlas entry: {entry:?}"); match entry { Entry::Contiguous(allocation) => { @@ -258,7 +258,7 @@ impl Atlas { } fn deallocate(&mut self, allocation: &Allocation) { - log::info!("Deallocating atlas: {:?}", allocation); + log::info!("Deallocating atlas: {allocation:?}"); match allocation { Allocation::Full { layer } => { diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 2c03d36b..6582bb82 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -152,7 +152,7 @@ impl Cache { let allocation = atlas.upload(device, encoder, width, height, &rgba)?; - log::debug!("allocating {} {}x{}", id, width, height); + log::debug!("allocating {id} {width}x{height}"); let _ = self.svg_hits.insert(id); let _ = self.rasterized_hits.insert(key); diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index a9521a15..1ddbe5fe 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -35,7 +35,7 @@ impl Compositor { ..Default::default() }); - log::info!("{:#?}", settings); + log::info!("{settings:#?}"); #[cfg(not(target_arch = "wasm32"))] if log::max_level() >= log::LevelFilter::Info { @@ -43,7 +43,7 @@ impl Compositor { .enumerate_adapters(settings.internal_backend) .map(|adapter| adapter.get_info()) .collect(); - log::info!("Available adapters: {:#?}", available_adapters); + log::info!("Available adapters: {available_adapters:#?}"); } #[allow(unsafe_code)] @@ -83,7 +83,7 @@ impl Compositor { }) })?; - log::info!("Selected format: {:?}", format); + log::info!("Selected format: {format:?}"); #[cfg(target_arch = "wasm32")] let limits = [wgpu::Limits::downlevel_webgl2_defaults() diff --git a/winit/src/application.rs b/winit/src/application.rs index d1689452..dec77e80 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -157,7 +157,7 @@ where ) .with_visible(false); - log::debug!("Window builder: {:#?}", builder); + log::debug!("Window builder: {builder:#?}"); let window = builder .build(&event_loop) @@ -174,7 +174,7 @@ where let body = document.body().unwrap(); let target = target.and_then(|target| { - body.query_selector(&format!("#{}", target)) + body.query_selector(&format!("#{target}")) .ok() .unwrap_or(None) }); diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 7271441d..8b6c83ce 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -45,7 +45,7 @@ impl Clipboard { State::Connected(clipboard) => match clipboard.write(contents) { Ok(()) => {} Err(error) => { - log::warn!("error writing to clipboard: {}", error) + log::warn!("error writing to clipboard: {error}") } }, State::Unavailable => {} -- cgit From c997aad85d7ee6e77085e50e5e599002549d228f Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Tue, 19 Sep 2023 01:46:46 -0400 Subject: Chore: Apply clippy map transformations Convert `.map().unwrap_or()` to `.map_or()` and similar transformations. --- core/src/mouse/click.rs | 4 +--- examples/integration/src/main.rs | 6 ++++-- examples/screenshot/src/main.rs | 5 +---- wgpu/src/image/vector.rs | 2 +- wgpu/src/triangle.rs | 2 +- winit/src/application/state.rs | 3 +-- winit/src/clipboard.rs | 3 +-- 7 files changed, 10 insertions(+), 15 deletions(-) diff --git a/core/src/mouse/click.rs b/core/src/mouse/click.rs index 4a7d796c..240e5c64 100644 --- a/core/src/mouse/click.rs +++ b/core/src/mouse/click.rs @@ -69,8 +69,6 @@ impl Click { }; self.position == new_position - && duration - .map(|duration| duration.as_millis() <= 300) - .unwrap_or(false) + && duration.is_some_and(|duration| duration.as_millis() <= 300) } } diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 7945bd20..243297b2 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -200,8 +200,10 @@ pub fn main() -> Result<(), Box> { viewport.scale_factor(), ) }) - .map(mouse::Cursor::Available) - .unwrap_or(mouse::Cursor::Unavailable), + .map_or( + mouse::Cursor::Unavailable, + mouse::Cursor::Available, + ), &mut renderer, &Theme::Dark, &renderer::Style { diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 68c9d031..4dc1b051 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -293,10 +293,7 @@ fn numeric_input( ) -> Element<'_, Option> { text_input( placeholder, - &value - .as_ref() - .map(ToString::to_string) - .unwrap_or_else(String::new), + &value.as_ref().map_or_else(String::new, ToString::to_string), ) .on_input(move |text| { if text.is_empty() { diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 2c03d36b..1ac82bc7 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -56,7 +56,7 @@ impl Cache { .ok() }); - tree.map(Svg::Loaded).unwrap_or(Svg::NotFound) + tree.map_or(Svg::NotFound, Svg::Loaded) } svg::Data::Bytes(bytes) => { match usvg::Tree::from_data(bytes, &usvg::Options::default()) { diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index d430e607..7e1bd9cc 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -349,7 +349,7 @@ fn multisample_state( antialiasing: Option, ) -> wgpu::MultisampleState { wgpu::MultisampleState { - count: antialiasing.map(|a| a.sample_count()).unwrap_or(1), + count: antialiasing.map_or(1, Antialiasing::sample_count), mask: !0, alpha_to_coverage_enabled: false, } diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index e655529a..9d1d5dcf 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -97,8 +97,7 @@ where self.viewport.scale_factor(), ) }) - .map(mouse::Cursor::Available) - .unwrap_or(mouse::Cursor::Unavailable) + .map_or(mouse::Cursor::Unavailable, mouse::Cursor::Available) } /// Returns the current keyboard modifiers of the [`State`]. diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 7271441d..14bbcbee 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -17,8 +17,7 @@ impl Clipboard { pub fn connect(window: &winit::window::Window) -> Clipboard { let state = window_clipboard::Clipboard::connect(window) .ok() - .map(State::Connected) - .unwrap_or(State::Unavailable); + .map_or(State::Unavailable, State::Connected); Clipboard { state } } -- cgit From c6554d990770b941b5003d6ef40af3f9dedcd052 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Tue, 19 Sep 2023 01:50:05 -0400 Subject: Chore: Apply clippy docs keyword quoting Add quotes a number of doc strings like `sRGB` --- core/src/color.rs | 2 +- core/src/window/icon.rs | 2 +- futures/src/subscription.rs | 4 ++-- graphics/src/compositor.rs | 2 +- runtime/src/window/screenshot.rs | 2 +- style/src/rule.rs | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index 1392f28b..0e8b7475 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -1,7 +1,7 @@ #[cfg(feature = "palette")] use palette::rgb::{Srgb, Srgba}; -/// A color in the sRGB color space. +/// A color in the `sRGB` color space. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Color { /// Red component, 0.0 - 1.0 diff --git a/core/src/window/icon.rs b/core/src/window/icon.rs index 2fc48e3b..5ef0eed7 100644 --- a/core/src/window/icon.rs +++ b/core/src/window/icon.rs @@ -3,7 +3,7 @@ use crate::Size; use std::mem; -/// Builds an [`Icon`] from its RGBA pixels in the sRGB color space. +/// Builds an [`Icon`] from its RGBA pixels in the `sRGB` color space. pub fn from_rgba( rgba: Vec, width: u32, diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index d40125e3..7163248d 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -25,7 +25,7 @@ pub type EventStream = BoxStream<(Event, event::Status)>; /// A [`Subscription`] is normally provided to some runtime, like a `Command`, /// and it will generate events as long as the user keeps requesting it. /// -/// For instance, you can use a [`Subscription`] to listen to a WebSocket +/// For instance, you can use a [`Subscription`] to listen to a `WebSocket` /// connection, keyboard presses, mouse events, time ticks, etc. #[must_use = "`Subscription` must be returned to runtime to take effect"] pub struct Subscription { @@ -355,7 +355,7 @@ where /// } /// ``` /// -/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket +/// Check out the [`websocket`] example, which showcases this pattern to maintain a `WebSocket` /// connection open. /// /// [`websocket`]: https://github.com/iced-rs/iced/tree/0.10/examples/websocket diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 7173ffa7..0222a80f 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -61,7 +61,7 @@ pub trait Compositor: Sized { ) -> Result<(), SurfaceError>; /// Screenshots the current [`Renderer`] primitives to an offscreen texture, and returns the bytes of - /// the texture ordered as `RGBA` in the sRGB color space. + /// the texture ordered as `RGBA` in the `sRGB` color space. /// /// [`Renderer`]: Self::Renderer fn screenshot>( diff --git a/runtime/src/window/screenshot.rs b/runtime/src/window/screenshot.rs index c84286b6..21e04718 100644 --- a/runtime/src/window/screenshot.rs +++ b/runtime/src/window/screenshot.rs @@ -6,7 +6,7 @@ use std::sync::Arc; /// Data of a screenshot, captured with `window::screenshot()`. /// -/// The `bytes` of this screenshot will always be ordered as `RGBA` in the sRGB color space. +/// The `bytes` of this screenshot will always be ordered as `RGBA` in the `sRGB` color space. #[derive(Clone)] pub struct Screenshot { /// The bytes of the [`Screenshot`]. diff --git a/style/src/rule.rs b/style/src/rule.rs index afae085c..efbe7444 100644 --- a/style/src/rule.rs +++ b/style/src/rule.rs @@ -47,7 +47,7 @@ impl FillMode { /// /// # Returns /// - /// * (starting_offset, length) + /// * (`starting_offset`, `length`) pub fn fill(&self, space: f32) -> (f32, f32) { match *self { FillMode::Full => (0.0, space), -- cgit From efd0ff6ded4e647e5fad0964555dbed541a075d7 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Tue, 19 Sep 2023 01:52:25 -0400 Subject: Chore: Apply some minor clippy fixes * Use `.elapsed()` for duration * Use direct iteration without calling `.iter()` and the like * order fields in the `Text` struct creation as declared --- core/src/gradient.rs | 2 +- core/src/widget/text.rs | 2 +- examples/game_of_life/src/main.rs | 2 +- examples/tour/src/main.rs | 2 +- graphics/src/gradient.rs | 2 +- runtime/src/debug/basic.rs | 20 +++++++------------- tiny_skia/src/geometry.rs | 2 +- 7 files changed, 13 insertions(+), 19 deletions(-) diff --git a/core/src/gradient.rs b/core/src/gradient.rs index 4a0d5ea0..6a5533f8 100644 --- a/core/src/gradient.rs +++ b/core/src/gradient.rs @@ -94,7 +94,7 @@ impl Linear { mut self, stops: impl IntoIterator, ) -> Self { - for stop in stops.into_iter() { + for stop in stops { self = self.add_stop(stop.offset, stop.color) } diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 53ed463e..ba98f2d8 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -220,9 +220,9 @@ where size, line_height, font, - shaping, horizontal_alignment, vertical_alignment, + shaping, }, ); diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index e451cb06..e3089beb 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -793,7 +793,7 @@ mod grid { } } - for (cell, amount) in adjacent_life.iter() { + for (cell, amount) in &adjacent_life { match amount { 2 => {} 3 => { diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 3e3a8ad7..952300bb 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -482,7 +482,7 @@ impl<'a> Step { column( Language::all() .iter() - .cloned() + .copied() .map(|language| { radio( language, diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs index b274ec86..7460e12e 100644 --- a/graphics/src/gradient.rs +++ b/graphics/src/gradient.rs @@ -87,7 +87,7 @@ impl Linear { mut self, stops: impl IntoIterator, ) -> Self { - for stop in stops.into_iter() { + for stop in stops { self = self.add_stop(stop.offset, stop.color) } diff --git a/runtime/src/debug/basic.rs b/runtime/src/debug/basic.rs index 32f725a1..4c994a2f 100644 --- a/runtime/src/debug/basic.rs +++ b/runtime/src/debug/basic.rs @@ -75,7 +75,7 @@ impl Debug { } pub fn startup_finished(&mut self) { - self.startup_duration = time::Instant::now() - self.startup_start; + self.startup_duration = self.startup_start.elapsed(); } pub fn update_started(&mut self) { @@ -83,8 +83,7 @@ impl Debug { } pub fn update_finished(&mut self) { - self.update_durations - .push(time::Instant::now() - self.update_start); + self.update_durations.push(self.update_start.elapsed()); } pub fn view_started(&mut self) { @@ -92,8 +91,7 @@ impl Debug { } pub fn view_finished(&mut self) { - self.view_durations - .push(time::Instant::now() - self.view_start); + self.view_durations.push(self.view_start.elapsed()); } pub fn layout_started(&mut self) { @@ -101,8 +99,7 @@ impl Debug { } pub fn layout_finished(&mut self) { - self.layout_durations - .push(time::Instant::now() - self.layout_start); + self.layout_durations.push(self.layout_start.elapsed()); } pub fn event_processing_started(&mut self) { @@ -110,8 +107,7 @@ impl Debug { } pub fn event_processing_finished(&mut self) { - self.event_durations - .push(time::Instant::now() - self.event_start); + self.event_durations.push(self.event_start.elapsed()); } pub fn draw_started(&mut self) { @@ -119,8 +115,7 @@ impl Debug { } pub fn draw_finished(&mut self) { - self.draw_durations - .push(time::Instant::now() - self.draw_start); + self.draw_durations.push(self.draw_start.elapsed()); } pub fn render_started(&mut self) { @@ -128,8 +123,7 @@ impl Debug { } pub fn render_finished(&mut self) { - self.render_durations - .push(time::Instant::now() - self.render_start); + self.render_durations.push(self.render_start.elapsed()); } pub fn log_message(&mut self, message: &Message) { diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 047bc0ff..1d573f6a 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -182,7 +182,7 @@ fn convert_path(path: &Path) -> Option { let mut builder = tiny_skia::PathBuilder::new(); let mut last_point = Default::default(); - for event in path.raw().iter() { + for event in path.raw() { match event { lyon_path::Event::Begin { at } => { builder.move_to(at.x, at.y); -- cgit From 06dc12bfbf75958c6534306b3d1b57ae47bdb37a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Sep 2023 19:35:28 +0200 Subject: Simplify `editor` example --- examples/editor/src/main.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index 277eb3e9..6def2082 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -4,7 +4,7 @@ use iced::widget::{ button, column, container, horizontal_space, pick_list, row, text, text_editor, tooltip, }; -use iced::{Application, Command, Element, Font, Length, Settings}; +use iced::{Alignment, Application, Command, Element, Font, Length, Settings}; use highlighter::Highlighter; @@ -169,7 +169,8 @@ impl Application for Editor { .text_size(14) .padding([5, 10]) ] - .spacing(10); + .spacing(10) + .align_items(Alignment::Center); let status = row![ text(if let Some(path) = &self.file { @@ -275,8 +276,7 @@ fn action<'a, Message: Clone + 'a>( label: &'a str, on_press: Option, ) -> Element<'a, Message> { - let action = - button(container(content).width(Length::Fill).center_x()).width(40); + let action = button(container(content).width(30).center_x()); if let Some(on_press) = on_press { tooltip( @@ -316,7 +316,7 @@ mod highlighter { use std::ops::Range; use syntect::highlighting; - use syntect::parsing::{self, SyntaxReference}; + use syntect::parsing; #[derive(Debug, Clone, PartialEq)] pub struct Settings { @@ -374,7 +374,7 @@ mod highlighter { pub struct Highlighter { syntaxes: parsing::SyntaxSet, - syntax: SyntaxReference, + syntax: parsing::SyntaxReference, theme: highlighting::Theme, caches: Vec<(parsing::ParseState, parsing::ScopeStack)>, current_line: usize, -- cgit From c0a141ab026f5686d6bd92c8807b174396cb9105 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Sep 2023 19:39:23 +0200 Subject: Save file on `Cmd+S` in `editor` example --- examples/editor/src/main.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index 6def2082..36d4287c 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -1,10 +1,14 @@ use iced::executor; +use iced::keyboard; use iced::theme::{self, Theme}; use iced::widget::{ button, column, container, horizontal_space, pick_list, row, text, text_editor, tooltip, }; -use iced::{Alignment, Application, Command, Element, Font, Length, Settings}; +use iced::{ + Alignment, Application, Command, Element, Font, Length, Settings, + Subscription, +}; use highlighter::Highlighter; @@ -147,6 +151,15 @@ impl Application for Editor { } } + fn subscription(&self) -> Subscription { + keyboard::on_key_press(|key_code, modifiers| match key_code { + keyboard::KeyCode::S if modifiers.command() => { + Some(Message::SaveFile) + } + _ => None, + }) + } + fn view(&self) -> Element { let controls = row![ action(new_icon(), "New file", Some(Message::NewFile)), -- cgit From f806d001e6fb44b5a45029ca257261e6e0d4d4b2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Sep 2023 20:48:50 +0200 Subject: Introduce new `iced_highlighter` subcrate --- Cargo.toml | 10 +- core/src/text/highlighter.rs | 11 +- examples/editor/Cargo.toml | 2 +- examples/editor/src/main.rs | 251 +++---------------------------------------- highlighter/Cargo.toml | 16 +++ highlighter/src/lib.rs | 225 ++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + style/src/text_editor.rs | 16 +-- widget/src/text_editor.rs | 28 ++++- 9 files changed, 302 insertions(+), 260 deletions(-) create mode 100644 highlighter/Cargo.toml create mode 100644 highlighter/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index e887afc0..8899fa67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,8 @@ system = ["iced_winit/system"] web-colors = ["iced_renderer/web-colors"] # Enables the WebGL backend, replacing WebGPU webgl = ["iced_renderer/webgl"] +# Enables the syntax `highlighter` module +highlighter = ["iced_highlighter"] # Enables the advanced module advanced = [] @@ -58,6 +60,9 @@ iced_widget.workspace = true iced_winit.features = ["application"] iced_winit.workspace = true +iced_highlighter.workspace = true +iced_highlighter.optional = true + thiserror.workspace = true image.workspace = true @@ -78,8 +83,9 @@ members = [ "core", "futures", "graphics", - "runtime", + "highlighter", "renderer", + "runtime", "style", "tiny_skia", "wgpu", @@ -103,6 +109,7 @@ iced = { version = "0.12", path = "." } iced_core = { version = "0.12", path = "core" } iced_futures = { version = "0.12", path = "futures" } iced_graphics = { version = "0.12", path = "graphics" } +iced_highlighter = { version = "0.12", path = "highlighter" } iced_renderer = { version = "0.12", path = "renderer" } iced_runtime = { version = "0.12", path = "runtime" } iced_style = { version = "0.12", path = "style" } @@ -137,6 +144,7 @@ resvg = "0.35" rustc-hash = "1.0" smol = "1.0" softbuffer = "0.2" +syntect = "5.1" sysinfo = "0.28" thiserror = "1.0" tiny-skia = "0.10" diff --git a/core/src/text/highlighter.rs b/core/src/text/highlighter.rs index b462d083..9a9cff89 100644 --- a/core/src/text/highlighter.rs +++ b/core/src/text/highlighter.rs @@ -52,8 +52,17 @@ impl Highlighter for PlainText { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Format { pub color: Option, pub font: Option, } + +impl Default for Format { + fn default() -> Self { + Self { + color: None, + font: None, + } + } +} diff --git a/examples/editor/Cargo.toml b/examples/editor/Cargo.toml index eeb34aa1..a77b1e9f 100644 --- a/examples/editor/Cargo.toml +++ b/examples/editor/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["advanced", "tokio", "debug"] +iced.features = ["highlighter", "tokio", "debug"] tokio.workspace = true tokio.features = ["fs"] diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index 36d4287c..d513090f 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -1,4 +1,5 @@ use iced::executor; +use iced::highlighter::{self, Highlighter}; use iced::keyboard; use iced::theme::{self, Theme}; use iced::widget::{ @@ -10,8 +11,6 @@ use iced::{ Subscription, }; -use highlighter::Highlighter; - use std::ffi; use std::io; use std::path::{Path, PathBuf}; @@ -210,16 +209,19 @@ impl Application for Editor { controls, text_editor(&self.content) .on_edit(Message::Edit) - .highlight::(highlighter::Settings { - theme: self.theme, - extension: self - .file - .as_deref() - .and_then(Path::extension) - .and_then(ffi::OsStr::to_str) - .map(str::to_string) - .unwrap_or(String::from("rs")), - }), + .highlight::( + highlighter::Settings { + theme: self.theme, + extension: self + .file + .as_deref() + .and_then(Path::extension) + .and_then(ffi::OsStr::to_str) + .map(str::to_string) + .unwrap_or(String::from("rs")), + }, + |highlight, _theme| highlight.to_format() + ), status, ] .spacing(10) @@ -321,228 +323,3 @@ fn icon<'a, Message>(codepoint: char) -> Element<'a, Message> { text(codepoint).font(ICON_FONT).into() } - -mod highlighter { - use iced::advanced::text::highlighter; - use iced::widget::text_editor; - use iced::{Color, Font}; - - use std::ops::Range; - use syntect::highlighting; - use syntect::parsing; - - #[derive(Debug, Clone, PartialEq)] - pub struct Settings { - pub theme: Theme, - pub extension: String, - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub enum Theme { - SolarizedDark, - InspiredGitHub, - Base16Mocha, - } - - impl Theme { - pub const ALL: &[Self] = - &[Self::SolarizedDark, Self::InspiredGitHub, Self::Base16Mocha]; - - fn key(&self) -> &'static str { - match self { - Theme::InspiredGitHub => "InspiredGitHub", - Theme::Base16Mocha => "base16-mocha.dark", - Theme::SolarizedDark => "Solarized (dark)", - } - } - } - - impl std::fmt::Display for Theme { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Theme::InspiredGitHub => write!(f, "Inspired GitHub"), - Theme::Base16Mocha => write!(f, "Mocha"), - Theme::SolarizedDark => write!(f, "Solarized Dark"), - } - } - } - - pub struct Highlight(highlighting::StyleModifier); - - impl text_editor::Highlight for Highlight { - fn format(&self, _theme: &iced::Theme) -> highlighter::Format { - highlighter::Format { - color: self.0.foreground.map(|color| { - Color::from_rgba8( - color.r, - color.g, - color.b, - color.a as f32 / 255.0, - ) - }), - font: None, - } - } - } - - pub struct Highlighter { - syntaxes: parsing::SyntaxSet, - syntax: parsing::SyntaxReference, - theme: highlighting::Theme, - caches: Vec<(parsing::ParseState, parsing::ScopeStack)>, - current_line: usize, - } - - const LINES_PER_SNAPSHOT: usize = 50; - - impl highlighter::Highlighter for Highlighter { - type Settings = Settings; - type Highlight = Highlight; - - type Iterator<'a> = - Box, Self::Highlight)> + 'a>; - - fn new(settings: &Self::Settings) -> Self { - let syntaxes = parsing::SyntaxSet::load_defaults_nonewlines(); - - let syntax = syntaxes - .find_syntax_by_token(&settings.extension) - .unwrap_or_else(|| syntaxes.find_syntax_plain_text()); - - let theme = highlighting::ThemeSet::load_defaults() - .themes - .remove(settings.theme.key()) - .unwrap(); - - let parser = parsing::ParseState::new(syntax); - let stack = parsing::ScopeStack::new(); - - Highlighter { - syntax: syntax.clone(), - syntaxes, - theme, - caches: vec![(parser, stack)], - current_line: 0, - } - } - - fn update(&mut self, new_settings: &Self::Settings) { - self.syntax = self - .syntaxes - .find_syntax_by_token(&new_settings.extension) - .unwrap_or_else(|| self.syntaxes.find_syntax_plain_text()) - .clone(); - - self.theme = highlighting::ThemeSet::load_defaults() - .themes - .remove(new_settings.theme.key()) - .unwrap(); - - // Restart the highlighter - self.change_line(0); - } - - fn change_line(&mut self, line: usize) { - let snapshot = line / LINES_PER_SNAPSHOT; - - if snapshot <= self.caches.len() { - self.caches.truncate(snapshot); - self.current_line = snapshot * LINES_PER_SNAPSHOT; - } else { - self.caches.truncate(1); - self.current_line = 0; - } - - let (parser, stack) = - self.caches.last().cloned().unwrap_or_else(|| { - ( - parsing::ParseState::new(&self.syntax), - parsing::ScopeStack::new(), - ) - }); - - self.caches.push((parser, stack)); - } - - fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_> { - if self.current_line / LINES_PER_SNAPSHOT >= self.caches.len() { - let (parser, stack) = - self.caches.last().expect("Caches must not be empty"); - - self.caches.push((parser.clone(), stack.clone())); - } - - self.current_line += 1; - - let (parser, stack) = - self.caches.last_mut().expect("Caches must not be empty"); - - let ops = - parser.parse_line(line, &self.syntaxes).unwrap_or_default(); - - let highlighter = highlighting::Highlighter::new(&self.theme); - - Box::new( - ScopeRangeIterator { - ops, - line_length: line.len(), - index: 0, - last_str_index: 0, - } - .filter_map(move |(range, scope)| { - let _ = stack.apply(&scope); - - if range.is_empty() { - None - } else { - Some(( - range, - Highlight( - highlighter.style_mod_for_stack(&stack.scopes), - ), - )) - } - }), - ) - } - - fn current_line(&self) -> usize { - self.current_line - } - } - - pub struct ScopeRangeIterator { - ops: Vec<(usize, parsing::ScopeStackOp)>, - line_length: usize, - index: usize, - last_str_index: usize, - } - - impl Iterator for ScopeRangeIterator { - type Item = (std::ops::Range, parsing::ScopeStackOp); - - fn next(&mut self) -> Option { - if self.index > self.ops.len() { - return None; - } - - let next_str_i = if self.index == self.ops.len() { - self.line_length - } else { - self.ops[self.index].0 - }; - - let range = self.last_str_index..next_str_i; - self.last_str_index = next_str_i; - - let op = if self.index == 0 { - parsing::ScopeStackOp::Noop - } else { - self.ops[self.index - 1].1.clone() - }; - - self.index += 1; - Some((range, op)) - } - } -} diff --git a/highlighter/Cargo.toml b/highlighter/Cargo.toml new file mode 100644 index 00000000..311d2998 --- /dev/null +++ b/highlighter/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "iced_highlighter" +description = "A syntax higlighter for iced" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true + +[dependencies] +iced_core.workspace = true + +syntect.workspace = true diff --git a/highlighter/src/lib.rs b/highlighter/src/lib.rs new file mode 100644 index 00000000..79cfafcd --- /dev/null +++ b/highlighter/src/lib.rs @@ -0,0 +1,225 @@ +use iced_core as core; + +use crate::core::text::highlighter::{self, Format}; +use crate::core::{Color, Font}; + +use std::ops::Range; +use syntect::highlighting; +use syntect::parsing; + +pub struct Highlighter { + syntaxes: parsing::SyntaxSet, + syntax: parsing::SyntaxReference, + theme: highlighting::Theme, + caches: Vec<(parsing::ParseState, parsing::ScopeStack)>, + current_line: usize, +} + +const LINES_PER_SNAPSHOT: usize = 50; + +impl highlighter::Highlighter for Highlighter { + type Settings = Settings; + type Highlight = Highlight; + + type Iterator<'a> = + Box, Self::Highlight)> + 'a>; + + fn new(settings: &Self::Settings) -> Self { + let syntaxes = parsing::SyntaxSet::load_defaults_nonewlines(); + + let syntax = syntaxes + .find_syntax_by_token(&settings.extension) + .unwrap_or_else(|| syntaxes.find_syntax_plain_text()); + + let theme = highlighting::ThemeSet::load_defaults() + .themes + .remove(settings.theme.key()) + .unwrap(); + + let parser = parsing::ParseState::new(syntax); + let stack = parsing::ScopeStack::new(); + + Highlighter { + syntax: syntax.clone(), + syntaxes, + theme, + caches: vec![(parser, stack)], + current_line: 0, + } + } + + fn update(&mut self, new_settings: &Self::Settings) { + self.syntax = self + .syntaxes + .find_syntax_by_token(&new_settings.extension) + .unwrap_or_else(|| self.syntaxes.find_syntax_plain_text()) + .clone(); + + self.theme = highlighting::ThemeSet::load_defaults() + .themes + .remove(new_settings.theme.key()) + .unwrap(); + + // Restart the highlighter + self.change_line(0); + } + + fn change_line(&mut self, line: usize) { + let snapshot = line / LINES_PER_SNAPSHOT; + + if snapshot <= self.caches.len() { + self.caches.truncate(snapshot); + self.current_line = snapshot * LINES_PER_SNAPSHOT; + } else { + self.caches.truncate(1); + self.current_line = 0; + } + + let (parser, stack) = + self.caches.last().cloned().unwrap_or_else(|| { + ( + parsing::ParseState::new(&self.syntax), + parsing::ScopeStack::new(), + ) + }); + + self.caches.push((parser, stack)); + } + + fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_> { + if self.current_line / LINES_PER_SNAPSHOT >= self.caches.len() { + let (parser, stack) = + self.caches.last().expect("Caches must not be empty"); + + self.caches.push((parser.clone(), stack.clone())); + } + + self.current_line += 1; + + let (parser, stack) = + self.caches.last_mut().expect("Caches must not be empty"); + + let ops = parser.parse_line(line, &self.syntaxes).unwrap_or_default(); + + let highlighter = highlighting::Highlighter::new(&self.theme); + + Box::new( + ScopeRangeIterator { + ops, + line_length: line.len(), + index: 0, + last_str_index: 0, + } + .filter_map(move |(range, scope)| { + let _ = stack.apply(&scope); + + if range.is_empty() { + None + } else { + Some(( + range, + Highlight( + highlighter.style_mod_for_stack(&stack.scopes), + ), + )) + } + }), + ) + } + + fn current_line(&self) -> usize { + self.current_line + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Settings { + pub theme: Theme, + pub extension: String, +} + +pub struct Highlight(highlighting::StyleModifier); + +impl Highlight { + pub fn color(&self) -> Option { + self.0.foreground.map(|color| { + Color::from_rgba8(color.r, color.g, color.b, color.a as f32 / 255.0) + }) + } + + pub fn font(&self) -> Option { + None + } + + pub fn to_format(&self) -> Format { + Format { + color: self.color(), + font: self.font(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Theme { + SolarizedDark, + InspiredGitHub, + Base16Mocha, +} + +impl Theme { + pub const ALL: &[Self] = + &[Self::SolarizedDark, Self::InspiredGitHub, Self::Base16Mocha]; + + fn key(&self) -> &'static str { + match self { + Theme::InspiredGitHub => "InspiredGitHub", + Theme::Base16Mocha => "base16-mocha.dark", + Theme::SolarizedDark => "Solarized (dark)", + } + } +} + +impl std::fmt::Display for Theme { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Theme::InspiredGitHub => write!(f, "Inspired GitHub"), + Theme::Base16Mocha => write!(f, "Mocha"), + Theme::SolarizedDark => write!(f, "Solarized Dark"), + } + } +} + +pub struct ScopeRangeIterator { + ops: Vec<(usize, parsing::ScopeStackOp)>, + line_length: usize, + index: usize, + last_str_index: usize, +} + +impl Iterator for ScopeRangeIterator { + type Item = (std::ops::Range, parsing::ScopeStackOp); + + fn next(&mut self) -> Option { + if self.index > self.ops.len() { + return None; + } + + let next_str_i = if self.index == self.ops.len() { + self.line_length + } else { + self.ops[self.index].0 + }; + + let range = self.last_str_index..next_str_i; + self.last_str_index = next_str_i; + + let op = if self.index == 0 { + parsing::ScopeStackOp::Noop + } else { + self.ops[self.index - 1].1.clone() + }; + + self.index += 1; + Some((range, op)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 3cbe716a..e435a041 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -174,6 +174,9 @@ use iced_winit::runtime; pub use iced_futures::futures; +#[cfg(feature = "highlighter")] +pub use iced_highlighter as highlighter; + mod error; mod sandbox; diff --git a/style/src/text_editor.rs b/style/src/text_editor.rs index f1c31287..f6bae7e6 100644 --- a/style/src/text_editor.rs +++ b/style/src/text_editor.rs @@ -1,6 +1,5 @@ //! Change the appearance of a text editor. -use crate::core::text::highlighter; -use crate::core::{self, Background, BorderRadius, Color}; +use crate::core::{Background, BorderRadius, Color}; /// The appearance of a text input. #[derive(Debug, Clone, Copy)] @@ -46,16 +45,3 @@ pub trait StyleSheet { /// Produces the style of a disabled text input. fn disabled(&self, style: &Self::Style) -> Appearance; } - -pub trait Highlight { - fn format(&self, theme: &Theme) -> highlighter::Format; -} - -impl Highlight for () { - fn format(&self, _theme: &Theme) -> highlighter::Format { - highlighter::Format { - color: None, - font: None, - } - } -} diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index ad12a076..c384b8a2 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -16,7 +16,7 @@ use std::cell::RefCell; use std::ops::DerefMut; use std::sync::Arc; -pub use crate::style::text_editor::{Appearance, Highlight, StyleSheet}; +pub use crate::style::text_editor::{Appearance, StyleSheet}; pub use text::editor::{Action, Edit, Motion}; pub struct TextEditor<'a, Highlighter, Message, Renderer = crate::Renderer> @@ -35,6 +35,10 @@ where style: ::Style, on_edit: Option Message + 'a>>, highlighter_settings: Highlighter::Settings, + highlighter_format: fn( + &Highlighter::Highlight, + &Renderer::Theme, + ) -> highlighter::Format, } impl<'a, Message, Renderer> @@ -55,6 +59,9 @@ where style: Default::default(), on_edit: None, highlighter_settings: (), + highlighter_format: |_highlight, _theme| { + highlighter::Format::default() + }, } } } @@ -63,7 +70,6 @@ impl<'a, Highlighter, Message, Renderer> TextEditor<'a, Highlighter, Message, Renderer> where Highlighter: text::Highlighter, - Highlighter::Highlight: Highlight, Renderer: text::Renderer, Renderer::Theme: StyleSheet, { @@ -85,6 +91,10 @@ where pub fn highlight( self, settings: H::Settings, + to_format: fn( + &H::Highlight, + &Renderer::Theme, + ) -> highlighter::Format, ) -> TextEditor<'a, H, Message, Renderer> { TextEditor { content: self.content, @@ -97,6 +107,7 @@ where style: self.style, on_edit: self.on_edit, highlighter_settings: settings, + highlighter_format: to_format, } } } @@ -203,13 +214,13 @@ struct State { drag_click: Option, highlighter: RefCell, highlighter_settings: Highlighter::Settings, + highlighter_format_address: usize, } impl<'a, Highlighter, Message, Renderer> Widget for TextEditor<'a, Highlighter, Message, Renderer> where Highlighter: text::Highlighter, - Highlighter::Highlight: Highlight, Renderer: text::Renderer, Renderer::Theme: StyleSheet, { @@ -226,6 +237,7 @@ where &self.highlighter_settings, )), highlighter_settings: self.highlighter_settings.clone(), + highlighter_format_address: self.highlighter_format as usize, }) } @@ -246,6 +258,13 @@ where let mut internal = self.content.0.borrow_mut(); let state = tree.state.downcast_mut::>(); + if state.highlighter_format_address != self.highlighter_format as usize + { + state.highlighter.borrow_mut().change_line(0); + + state.highlighter_format_address = self.highlighter_format as usize; + } + if state.highlighter_settings != self.highlighter_settings { state .highlighter @@ -354,7 +373,7 @@ where internal.editor.highlight( self.font.unwrap_or_else(|| renderer.default_font()), state.highlighter.borrow_mut().deref_mut(), - |highlight| highlight.format(theme), + |highlight| (self.highlighter_format)(highlight, theme), ); let is_disabled = self.on_edit.is_none(); @@ -458,7 +477,6 @@ impl<'a, Highlighter, Message, Renderer> for Element<'a, Message, Renderer> where Highlighter: text::Highlighter, - Highlighter::Highlight: Highlight, Message: 'a, Renderer: text::Renderer, Renderer::Theme: StyleSheet, -- cgit From 77db1699028cf50fb92b9282ffd1f73507fce974 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Sep 2023 20:55:39 +0200 Subject: Fix typo in `higlighter` (why is it so hard to spell?) --- highlighter/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/highlighter/Cargo.toml b/highlighter/Cargo.toml index 311d2998..488546c0 100644 --- a/highlighter/Cargo.toml +++ b/highlighter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_highlighter" -description = "A syntax higlighter for iced" +description = "A syntax highlighter for iced" version.workspace = true authors.workspace = true edition.workspace = true -- cgit From 01667446549d10fab18f3ca0306f278b0fe22b13 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Sep 2023 20:56:50 +0200 Subject: Add `iced_highlighter` to `document` workflow --- .github/workflows/document.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/document.yml b/.github/workflows/document.yml index 230c5cb0..62e28ca3 100644 --- a/.github/workflows/document.yml +++ b/.github/workflows/document.yml @@ -15,6 +15,7 @@ jobs: RUSTDOCFLAGS="--cfg docsrs" \ cargo doc --no-deps --all-features \ -p iced_core \ + -p iced_highlighter \ -p iced_style \ -p iced_futures \ -p iced_runtime \ -- cgit From d9fbecf0d80234d63e7e5711f28fc35ee75fa503 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Sep 2023 20:58:15 +0200 Subject: Remove `syntect` dependency from `editor` example --- examples/editor/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/editor/Cargo.toml b/examples/editor/Cargo.toml index a77b1e9f..a3f6ea3b 100644 --- a/examples/editor/Cargo.toml +++ b/examples/editor/Cargo.toml @@ -12,5 +12,4 @@ iced.features = ["highlighter", "tokio", "debug"] tokio.workspace = true tokio.features = ["fs"] -syntect = "5.1" rfd = "0.12" -- cgit From a9ee8f62fdd0f74976947c21199684829aa8a496 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Sep 2023 21:57:09 +0200 Subject: Reuse syntaxes and themes lazily in `iced_highlighter` --- highlighter/Cargo.toml | 1 + highlighter/src/lib.rs | 51 +++++++++++++++++++++++++------------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/highlighter/Cargo.toml b/highlighter/Cargo.toml index 488546c0..2d108d6f 100644 --- a/highlighter/Cargo.toml +++ b/highlighter/Cargo.toml @@ -13,4 +13,5 @@ keywords.workspace = true [dependencies] iced_core.workspace = true +once_cell.workspace = true syntect.workspace = true diff --git a/highlighter/src/lib.rs b/highlighter/src/lib.rs index 79cfafcd..b80d6499 100644 --- a/highlighter/src/lib.rs +++ b/highlighter/src/lib.rs @@ -3,20 +3,26 @@ use iced_core as core; use crate::core::text::highlighter::{self, Format}; use crate::core::{Color, Font}; +use once_cell::sync::Lazy; use std::ops::Range; use syntect::highlighting; use syntect::parsing; +static SYNTAXES: Lazy = + Lazy::new(|| parsing::SyntaxSet::load_defaults_nonewlines()); + +static THEMES: Lazy = + Lazy::new(|| highlighting::ThemeSet::load_defaults()); + +const LINES_PER_SNAPSHOT: usize = 50; + pub struct Highlighter { - syntaxes: parsing::SyntaxSet, - syntax: parsing::SyntaxReference, - theme: highlighting::Theme, + syntax: &'static parsing::SyntaxReference, + highlighter: highlighting::Highlighter<'static>, caches: Vec<(parsing::ParseState, parsing::ScopeStack)>, current_line: usize, } -const LINES_PER_SNAPSHOT: usize = 50; - impl highlighter::Highlighter for Highlighter { type Settings = Settings; type Highlight = Highlight; @@ -25,40 +31,33 @@ impl highlighter::Highlighter for Highlighter { Box, Self::Highlight)> + 'a>; fn new(settings: &Self::Settings) -> Self { - let syntaxes = parsing::SyntaxSet::load_defaults_nonewlines(); - - let syntax = syntaxes + let syntax = SYNTAXES .find_syntax_by_token(&settings.extension) - .unwrap_or_else(|| syntaxes.find_syntax_plain_text()); + .unwrap_or_else(|| SYNTAXES.find_syntax_plain_text()); - let theme = highlighting::ThemeSet::load_defaults() - .themes - .remove(settings.theme.key()) - .unwrap(); + let highlighter = highlighting::Highlighter::new( + &THEMES.themes[settings.theme.key()], + ); let parser = parsing::ParseState::new(syntax); let stack = parsing::ScopeStack::new(); Highlighter { - syntax: syntax.clone(), - syntaxes, - theme, + syntax, + highlighter, caches: vec![(parser, stack)], current_line: 0, } } fn update(&mut self, new_settings: &Self::Settings) { - self.syntax = self - .syntaxes + self.syntax = SYNTAXES .find_syntax_by_token(&new_settings.extension) - .unwrap_or_else(|| self.syntaxes.find_syntax_plain_text()) - .clone(); + .unwrap_or_else(|| SYNTAXES.find_syntax_plain_text()); - self.theme = highlighting::ThemeSet::load_defaults() - .themes - .remove(new_settings.theme.key()) - .unwrap(); + self.highlighter = highlighting::Highlighter::new( + &THEMES.themes[new_settings.theme.key()], + ); // Restart the highlighter self.change_line(0); @@ -99,9 +98,9 @@ impl highlighter::Highlighter for Highlighter { let (parser, stack) = self.caches.last_mut().expect("Caches must not be empty"); - let ops = parser.parse_line(line, &self.syntaxes).unwrap_or_default(); + let ops = parser.parse_line(line, &SYNTAXES).unwrap_or_default(); - let highlighter = highlighting::Highlighter::new(&self.theme); + let highlighter = &self.highlighter; Box::new( ScopeRangeIterator { -- cgit From 9af0a27e675b71164f32f8d82eb4cde9cdd459f3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Sep 2023 22:28:28 +0200 Subject: Draw colored glyphs in `iced_tiny_skia` --- tiny_skia/src/text.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 96cfbf32..d1b33293 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -188,7 +188,7 @@ fn draw( if let Some((buffer, placement)) = glyph_cache.allocate( physical_glyph.cache_key, - color, + glyph.color_opt.map(from_color).unwrap_or(color), font_system, &mut swash, ) { @@ -213,6 +213,12 @@ fn draw( } } +fn from_color(color: cosmic_text::Color) -> Color { + let [r, g, b, a] = color.as_rgba(); + + Color::from_rgba8(r, g, b, a as f32 / 255.0) +} + #[derive(Debug, Clone, Default)] struct GlyphCache { entries: FxHashMap< -- cgit From be340a8cd822be1ea0fe4c1b1f3a62ca66d705b4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 19 Sep 2023 23:00:20 +0200 Subject: Fix gamma correction for colored glyphs in `iced_wgpu` --- core/src/color.rs | 20 ++++++++++++++++++++ graphics/src/text.rs | 12 +++++++++--- tiny_skia/src/text.rs | 14 +++++++++++++- wgpu/src/text.rs | 13 ++----------- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index 1392f28b..cce8b340 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -89,6 +89,26 @@ impl Color { } } + /// Creates a [`Color`] from its linear RGBA components. + pub fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self { + // As described in: + // https://en.wikipedia.org/wiki/SRGB + fn gamma_component(u: f32) -> f32 { + if u < 0.0031308 { + 12.92 * u + } else { + 1.055 * u.powf(1.0 / 2.4) - 0.055 + } + } + + Self { + r: gamma_component(r), + g: gamma_component(g), + b: gamma_component(b), + a, + } + } + /// Converts the [`Color`] into its RGBA8 equivalent. #[must_use] pub fn into_rgba8(self) -> [u8; 4] { diff --git a/graphics/src/text.rs b/graphics/src/text.rs index 5fcfc699..c10eacad 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -8,6 +8,7 @@ pub use paragraph::Paragraph; pub use cosmic_text; +use crate::color; use crate::core::font::{self, Font}; use crate::core::text::Shaping; use crate::core::{Color, Size}; @@ -131,7 +132,12 @@ pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping { } pub fn to_color(color: Color) -> cosmic_text::Color { - let [r, g, b, a] = color.into_rgba8(); - - cosmic_text::Color::rgba(r, g, b, a) + let [r, g, b, a] = color::pack(color).components(); + + cosmic_text::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) } diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index d1b33293..70e95d01 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -1,6 +1,7 @@ use crate::core::alignment; use crate::core::text::{LineHeight, Shaping}; use crate::core::{Color, Font, Pixels, Point, Rectangle}; +use crate::graphics::color; use crate::graphics::text::cache::{self, Cache}; use crate::graphics::text::editor; use crate::graphics::text::font_system; @@ -216,7 +217,18 @@ fn draw( fn from_color(color: cosmic_text::Color) -> Color { let [r, g, b, a] = color.as_rgba(); - Color::from_rgba8(r, g, b, a as f32 / 255.0) + if color::GAMMA_CORRECTION { + // `cosmic_text::Color` is linear RGB in this case, so we + // need to convert back to sRGB + Color::from_linear_rgba( + r as f32 / 255.0, + g as f32 / 255.0, + b as f32 / 255.0, + a as f32 / 255.0, + ) + } else { + Color::from_rgba8(r, g, b, a as f32 / 255.0) + } } #[derive(Debug, Clone, Default)] diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 581df0cb..f746be63 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -2,7 +2,7 @@ use crate::core::alignment; use crate::core::{Rectangle, Size}; use crate::graphics::color; use crate::graphics::text::cache::{self, Cache}; -use crate::graphics::text::{font_system, Editor, Paragraph}; +use crate::graphics::text::{font_system, to_color, Editor, Paragraph}; use crate::layer::Text; use std::borrow::Cow; @@ -214,16 +214,7 @@ impl Pipeline { right: (clip_bounds.x + clip_bounds.width) as i32, bottom: (clip_bounds.y + clip_bounds.height) as i32, }, - default_color: { - let [r, g, b, a] = color::pack(color).components(); - - glyphon::Color::rgba( - (r * 255.0) as u8, - (g * 255.0) as u8, - (b * 255.0) as u8, - (a * 255.0) as u8, - ) - }, + default_color: to_color(color), }) }, ); -- cgit From 93d6f748f69fc4ccf6c18f95c5f16b369c776da0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 01:13:36 +0200 Subject: Fix `clippy` lints in `iced_highlighter` --- highlighter/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/highlighter/src/lib.rs b/highlighter/src/lib.rs index b80d6499..f5a4fae5 100644 --- a/highlighter/src/lib.rs +++ b/highlighter/src/lib.rs @@ -9,10 +9,10 @@ use syntect::highlighting; use syntect::parsing; static SYNTAXES: Lazy = - Lazy::new(|| parsing::SyntaxSet::load_defaults_nonewlines()); + Lazy::new(parsing::SyntaxSet::load_defaults_nonewlines); static THEMES: Lazy = - Lazy::new(|| highlighting::ThemeSet::load_defaults()); + Lazy::new(highlighting::ThemeSet::load_defaults); const LINES_PER_SNAPSHOT: usize = 50; @@ -77,7 +77,7 @@ impl highlighter::Highlighter for Highlighter { let (parser, stack) = self.caches.last().cloned().unwrap_or_else(|| { ( - parsing::ParseState::new(&self.syntax), + parsing::ParseState::new(self.syntax), parsing::ScopeStack::new(), ) }); -- cgit From ff78e97ad7df4db3b2a97b94e99854f2f9e3021a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 01:21:42 +0200 Subject: Introduce more themes to `iced_highlighter` --- examples/editor/src/main.rs | 6 +++++- highlighter/src/lib.rs | 35 ++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index d513090f..f49ca6e8 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -230,7 +230,11 @@ impl Application for Editor { } fn theme(&self) -> Theme { - Theme::Dark + if self.theme.is_dark() { + Theme::Dark + } else { + Theme::Light + } } } diff --git a/highlighter/src/lib.rs b/highlighter/src/lib.rs index f5a4fae5..db28b5b1 100644 --- a/highlighter/src/lib.rs +++ b/highlighter/src/lib.rs @@ -161,19 +161,38 @@ impl Highlight { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Theme { SolarizedDark, - InspiredGitHub, Base16Mocha, + Base16Ocean, + Base16Eighties, + InspiredGitHub, } impl Theme { - pub const ALL: &[Self] = - &[Self::SolarizedDark, Self::InspiredGitHub, Self::Base16Mocha]; + pub const ALL: &[Self] = &[ + Self::SolarizedDark, + Self::Base16Mocha, + Self::Base16Ocean, + Self::Base16Eighties, + Self::InspiredGitHub, + ]; + + pub fn is_dark(self) -> bool { + match self { + Self::SolarizedDark + | Self::Base16Mocha + | Self::Base16Ocean + | Self::Base16Eighties => true, + Self::InspiredGitHub => false, + } + } fn key(&self) -> &'static str { match self { - Theme::InspiredGitHub => "InspiredGitHub", - Theme::Base16Mocha => "base16-mocha.dark", Theme::SolarizedDark => "Solarized (dark)", + Theme::Base16Mocha => "base16-mocha.dark", + Theme::Base16Ocean => "base16-ocean.dark", + Theme::Base16Eighties => "base16-eighties.dark", + Theme::InspiredGitHub => "InspiredGitHub", } } } @@ -181,9 +200,11 @@ impl Theme { impl std::fmt::Display for Theme { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Theme::InspiredGitHub => write!(f, "Inspired GitHub"), - Theme::Base16Mocha => write!(f, "Mocha"), Theme::SolarizedDark => write!(f, "Solarized Dark"), + Theme::Base16Mocha => write!(f, "Mocha"), + Theme::Base16Ocean => write!(f, "Ocean"), + Theme::Base16Eighties => write!(f, "Eighties"), + Theme::InspiredGitHub => write!(f, "Inspired GitHub"), } } } -- cgit From 29fb4eab878a7ba399cae6ab1ec18a71e369ee59 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 01:23:50 +0200 Subject: Scroll `TextEditor` only if `cursor.is_over(bounds)` --- widget/src/text_editor.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index c384b8a2..4191e02c 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -539,7 +539,9 @@ impl Update { } _ => None, }, - mouse::Event::WheelScrolled { delta } => { + mouse::Event::WheelScrolled { delta } + if cursor.is_over(bounds) => + { action(Action::Scroll { lines: match delta { mouse::ScrollDelta::Lines { y, .. } => { -- cgit From 9991052ce5617abf0f270bc9a74627ab6d45b35d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 04:07:20 +0200 Subject: Create `cargo lint` alias --- .cargo/config.toml | 2 ++ .github/workflows/lint.yml | 2 +- .gitignore | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..08884888 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +lint = "clippy --workspace --no-deps -- -D warnings -D clippy::semicolon_if_nothing_returned" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6fd98374..7fdc8867 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,4 +9,4 @@ jobs: components: clippy - uses: actions/checkout@master - name: Check lints - run: cargo clippy --workspace --all-features --all-targets --no-deps -- -D warnings + run: cargo lint diff --git a/.gitignore b/.gitignore index 0c46184f..f05ec438 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,5 @@ target/ pkg/ **/*.rs.bk Cargo.lock -.cargo/ dist/ traces/ -- cgit From 34f07b60273d6cfe13834af54cd0e24d34569387 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 04:11:52 +0200 Subject: Fix `clippy::semicolon_if_nothing_returned` --- core/src/element.rs | 6 +++--- core/src/gradient.rs | 2 +- core/src/hasher.rs | 2 +- core/src/overlay/element.rs | 6 +++--- core/src/overlay/group.rs | 2 +- core/src/shell.rs | 2 +- core/src/widget/operation/focusable.rs | 10 +++++----- core/src/widget/operation/scrollable.rs | 4 ++-- core/src/widget/operation/text_input.rs | 8 ++++---- core/src/widget/tree.rs | 4 ++-- examples/bezier_tool/src/main.rs | 2 +- examples/clock/src/main.rs | 2 +- examples/toast/src/main.rs | 2 +- examples/tooltip/src/main.rs | 2 +- examples/tour/src/main.rs | 2 +- graphics/src/geometry/path/builder.rs | 2 +- graphics/src/gradient.rs | 2 +- graphics/src/image.rs | 2 +- graphics/src/renderer.rs | 4 ++-- renderer/src/lib.rs | 4 ++-- runtime/src/overlay/nested.rs | 2 +- runtime/src/program/state.rs | 2 +- wgpu/src/buffer.rs | 2 +- wgpu/src/geometry.rs | 4 ++-- wgpu/src/image/atlas.rs | 2 +- wgpu/src/layer.rs | 2 +- widget/src/button.rs | 2 +- widget/src/column.rs | 2 +- widget/src/image.rs | 6 +++--- widget/src/image/viewer.rs | 2 +- widget/src/keyed/column.rs | 2 +- widget/src/lazy.rs | 6 +++--- widget/src/lazy/responsive.rs | 4 ++-- widget/src/pane_grid.rs | 4 ++-- widget/src/pane_grid/state.rs | 18 +++++++++--------- widget/src/pane_grid/title_bar.rs | 4 ++-- widget/src/pick_list.rs | 2 +- widget/src/row.rs | 4 ++-- widget/src/scrollable.rs | 10 +++++----- widget/src/slider.rs | 2 +- widget/src/text_input.rs | 24 ++++++++++++------------ widget/src/text_input/cursor.rs | 22 +++++++++++----------- widget/src/tooltip.rs | 2 +- widget/src/vertical_slider.rs | 2 +- winit/src/application.rs | 10 +++++----- winit/src/clipboard.rs | 4 ++-- 46 files changed, 108 insertions(+), 108 deletions(-) diff --git a/core/src/element.rs b/core/src/element.rs index 02f16bcb..dea111af 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -293,7 +293,7 @@ where } fn diff(&self, tree: &mut Tree) { - self.widget.diff(tree) + self.widget.diff(tree); } fn width(&self) -> Length { @@ -418,7 +418,7 @@ where viewport: &Rectangle, ) { self.widget - .draw(tree, renderer, theme, style, layout, cursor, viewport) + .draw(tree, renderer, theme, style, layout, cursor, viewport); } fn mouse_interaction( @@ -508,7 +508,7 @@ where ) { self.element .widget - .operate(state, layout, renderer, operation) + .operate(state, layout, renderer, operation); } fn on_event( diff --git a/core/src/gradient.rs b/core/src/gradient.rs index 6a5533f8..4711b044 100644 --- a/core/src/gradient.rs +++ b/core/src/gradient.rs @@ -95,7 +95,7 @@ impl Linear { stops: impl IntoIterator, ) -> Self { for stop in stops { - self = self.add_stop(stop.offset, stop.color) + self = self.add_stop(stop.offset, stop.color); } self diff --git a/core/src/hasher.rs b/core/src/hasher.rs index fa52f16d..9d8f75b3 100644 --- a/core/src/hasher.rs +++ b/core/src/hasher.rs @@ -4,7 +4,7 @@ pub struct Hasher(twox_hash::XxHash64); impl core::hash::Hasher for Hasher { fn write(&mut self, bytes: &[u8]) { - self.0.write(bytes) + self.0.write(bytes); } fn finish(&self) -> u64 { diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index 689e69be..3dd58f9b 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -98,7 +98,7 @@ where layout: Layout<'_>, cursor: mouse::Cursor, ) { - self.overlay.draw(renderer, theme, style, layout, cursor) + self.overlay.draw(renderer, theme, style, layout, cursor); } /// Applies a [`widget::Operation`] to the [`Element`]. @@ -205,7 +205,7 @@ where state: &mut dyn widget::operation::TextInput, id: Option<&widget::Id>, ) { - self.operation.text_input(state, id) + self.operation.text_input(state, id); } fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) { @@ -262,7 +262,7 @@ where layout: Layout<'_>, cursor: mouse::Cursor, ) { - self.content.draw(renderer, theme, style, layout, cursor) + self.content.draw(renderer, theme, style, layout, cursor); } fn is_over( diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index a0bae6bb..dccf6dba 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -143,7 +143,7 @@ where |(child, layout)| { child.operate(layout, renderer, operation); }, - ) + ); }); } diff --git a/core/src/shell.rs b/core/src/shell.rs index 246c937a..2952ceff 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -71,7 +71,7 @@ impl<'a, Message> Shell<'a, Message> { if self.is_layout_invalid { self.is_layout_invalid = false; - f() + f(); } } diff --git a/core/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs index ab1b677e..68c22faa 100644 --- a/core/src/widget/operation/focusable.rs +++ b/core/src/widget/operation/focusable.rs @@ -49,7 +49,7 @@ pub fn focus(target: Id) -> impl Operation { _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { - operate_on_children(self) + operate_on_children(self); } } @@ -85,7 +85,7 @@ where _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { - operate_on_children(self) + operate_on_children(self); } fn finish(&self) -> Outcome { @@ -132,7 +132,7 @@ pub fn focus_previous() -> impl Operation { _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { - operate_on_children(self) + operate_on_children(self); } } @@ -166,7 +166,7 @@ pub fn focus_next() -> impl Operation { _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { - operate_on_children(self) + operate_on_children(self); } } @@ -193,7 +193,7 @@ pub fn find_focused() -> impl Operation { _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { - operate_on_children(self) + operate_on_children(self); } fn finish(&self) -> Outcome { diff --git a/core/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs index 4f8b2a98..12161255 100644 --- a/core/src/widget/operation/scrollable.rs +++ b/core/src/widget/operation/scrollable.rs @@ -26,7 +26,7 @@ pub fn snap_to(target: Id, offset: RelativeOffset) -> impl Operation { _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { - operate_on_children(self) + operate_on_children(self); } fn scrollable( @@ -60,7 +60,7 @@ pub fn scroll_to(target: Id, offset: AbsoluteOffset) -> impl Operation { _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { - operate_on_children(self) + operate_on_children(self); } fn scrollable( diff --git a/core/src/widget/operation/text_input.rs b/core/src/widget/operation/text_input.rs index a9ea2e81..41731d4c 100644 --- a/core/src/widget/operation/text_input.rs +++ b/core/src/widget/operation/text_input.rs @@ -38,7 +38,7 @@ pub fn move_cursor_to_front(target: Id) -> impl Operation { _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { - operate_on_children(self) + operate_on_children(self); } } @@ -68,7 +68,7 @@ pub fn move_cursor_to_end(target: Id) -> impl Operation { _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { - operate_on_children(self) + operate_on_children(self); } } @@ -99,7 +99,7 @@ pub fn move_cursor_to(target: Id, position: usize) -> impl Operation { _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { - operate_on_children(self) + operate_on_children(self); } } @@ -128,7 +128,7 @@ pub fn select_all(target: Id) -> impl Operation { _bounds: Rectangle, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { - operate_on_children(self) + operate_on_children(self); } } diff --git a/core/src/widget/tree.rs b/core/src/widget/tree.rs index 202cca9a..d4b8828a 100644 --- a/core/src/widget/tree.rs +++ b/core/src/widget/tree.rs @@ -61,7 +61,7 @@ impl Tree { Renderer: crate::Renderer, { if self.tag == new.borrow().tag() { - new.borrow().diff(self) + new.borrow().diff(self); } else { *self = Self::new(new); } @@ -78,7 +78,7 @@ impl Tree { new_children, |tree, widget| tree.diff(widget.borrow()), |widget| Self::new(widget.borrow()), - ) + ); } /// Reconciliates the children of the tree with the provided list of widgets using custom diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 310be28f..9e4bc49c 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -81,7 +81,7 @@ mod bezier { } pub fn request_redraw(&mut self) { - self.cache.clear() + self.cache.clear(); } } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index fae77bc0..eec70dde 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -141,7 +141,7 @@ impl canvas::Program for Clock { frame.with_save(|frame| { frame.rotate(hand_rotation(self.now.second(), 60)); frame.stroke(&long_hand, thin_stroke()); - }) + }); }); vec![clock] diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 8570a38e..20c3dd42 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -639,7 +639,7 @@ mod toast { child .as_widget() .operate(state, layout, renderer, operation); - }) + }); }); } diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs index 35b862a8..a904cce0 100644 --- a/examples/tooltip/src/main.rs +++ b/examples/tooltip/src/main.rs @@ -40,7 +40,7 @@ impl Sandbox for Example { Position::Right => Position::FollowCursor, }; - self.position = position + self.position = position; } } } diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 952300bb..d46e40d1 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -285,7 +285,7 @@ impl<'a> Step { is_showing_icon, .. } = self { - *is_showing_icon = toggle + *is_showing_icon = toggle; } } }; diff --git a/graphics/src/geometry/path/builder.rs b/graphics/src/geometry/path/builder.rs index 794dd3bc..b0959fbf 100644 --- a/graphics/src/geometry/path/builder.rs +++ b/graphics/src/geometry/path/builder.rs @@ -174,7 +174,7 @@ impl Builder { /// the starting point. #[inline] pub fn close(&mut self) { - self.raw.close() + self.raw.close(); } /// Builds the [`Path`] of this [`Builder`]. diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs index 7460e12e..603f1b4a 100644 --- a/graphics/src/gradient.rs +++ b/graphics/src/gradient.rs @@ -88,7 +88,7 @@ impl Linear { stops: impl IntoIterator, ) -> Self { for stop in stops { - self = self.add_stop(stop.offset, stop.color) + self = self.add_stop(stop.offset, stop.color); } self diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 6b43f4a8..d89caace 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -79,7 +79,7 @@ impl Operation { use image::imageops; if self.contains(Self::FLIP_DIAGONALLY) { - imageops::flip_vertical_in_place(&mut image) + imageops::flip_vertical_in_place(&mut image); } if self.contains(Self::ROTATE_180) { diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index d4df29a5..a9d7895e 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -237,7 +237,7 @@ where } fn draw(&mut self, handle: image::Handle, bounds: Rectangle) { - self.primitives.push(Primitive::Image { handle, bounds }) + self.primitives.push(Primitive::Image { handle, bounds }); } } @@ -259,6 +259,6 @@ where handle, color, bounds, - }) + }); } } diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index ef5c4182..8c76f52e 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -60,7 +60,7 @@ impl Renderer { pub fn draw_mesh(&mut self, mesh: Mesh) { match self { Self::TinySkia(_) => { - log::warn!("Unsupported mesh primitive: {mesh:?}") + log::warn!("Unsupported mesh primitive: {mesh:?}"); } #[cfg(feature = "wgpu")] Self::Wgpu(renderer) => { @@ -241,7 +241,7 @@ impl crate::core::svg::Renderer for Renderer { color: Option, bounds: Rectangle, ) { - delegate!(self, renderer, renderer.draw(handle, color, bounds)) + delegate!(self, renderer, renderer.draw(handle, color, bounds)); } } diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index 21b6f7c1..062ccc72 100644 --- a/runtime/src/overlay/nested.rs +++ b/runtime/src/overlay/nested.rs @@ -164,7 +164,7 @@ where } } - recurse(&mut self.overlay, layout, renderer, operation) + recurse(&mut self.overlay, layout, renderer, operation); } /// Processes a runtime [`Event`]. diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index 9aa2d550..6f8f4063 100644 --- a/runtime/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -200,7 +200,7 @@ where match operation.finish() { operation::Outcome::None => {} operation::Outcome::Some(message) => { - self.queued_messages.push(message) + self.queued_messages.push(message); } operation::Outcome::Chain(next) => { current_operation = Some(next); diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs index 94122187..ef00c58f 100644 --- a/wgpu/src/buffer.rs +++ b/wgpu/src/buffer.rs @@ -87,7 +87,7 @@ impl Buffer { /// Clears any temporary data (i.e. offsets) from the buffer. pub fn clear(&mut self) { - self.offsets.clear() + self.offsets.clear(); } /// Returns the offset at `index`, if it exists. diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 63a59c05..655362b7 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -480,7 +480,7 @@ impl Frame { }, size: self.size, }), - )) + )); } } Buffer::Gradient(buffer) => { @@ -493,7 +493,7 @@ impl Frame { }, size: self.size, }), - )) + )); } } } diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 1253496b..e8ca4bd3 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -104,7 +104,7 @@ impl Atlas { padded_data[offset..offset + 4 * width as usize].copy_from_slice( &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], - ) + ); } match &entry { diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 7a5a0f7c..d20dbe66 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -202,7 +202,7 @@ impl<'a> Layer<'a> { translation, primitive, current_layer, - ) + ); } } Primitive::Clip { bounds, content } => { diff --git a/widget/src/button.rs b/widget/src/button.rs index 4915bd49..384a3156 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -146,7 +146,7 @@ where } fn diff(&self, tree: &mut Tree) { - tree.diff_children(std::slice::from_ref(&self.content)) + tree.diff_children(std::slice::from_ref(&self.content)); } fn width(&self) -> Length { diff --git a/widget/src/column.rs b/widget/src/column.rs index f2347cc9..42e90ac1 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -159,7 +159,7 @@ where child .as_widget() .operate(state, layout, renderer, operation); - }) + }); }); } diff --git a/widget/src/image.rs b/widget/src/image.rs index 3c83c87b..a0e89920 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -141,14 +141,14 @@ pub fn draw( ..bounds }; - renderer.draw(handle.clone(), drawing_bounds + offset) + renderer.draw(handle.clone(), drawing_bounds + offset); }; if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height { renderer.with_layer(bounds, render); } else { - render(renderer) + render(renderer); } } @@ -191,7 +191,7 @@ where _cursor: mouse::Cursor, _viewport: &Rectangle, ) { - draw(renderer, layout, &self.handle, self.content_fit) + draw(renderer, layout, &self.handle, self.content_fit); } } diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 724d121e..44624fc8 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -334,7 +334,7 @@ where y: bounds.y, ..Rectangle::with_size(image_size) }, - ) + ); }); }); } diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index 19016679..0ef82407 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -220,7 +220,7 @@ where child .as_widget() .operate(state, layout, renderer, operation); - }) + }); }); } diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index bf695a57..589dd938 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -135,7 +135,7 @@ where (*self.element.borrow_mut()) = Some(current.element.clone()); self.with_element(|element| { - tree.diff_children(std::slice::from_ref(&element.as_widget())) + tree.diff_children(std::slice::from_ref(&element.as_widget())); }); } else { (*self.element.borrow_mut()) = Some(current.element.clone()); @@ -243,8 +243,8 @@ where layout, cursor, viewport, - ) - }) + ); + }); } fn overlay<'b>( diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index 0b819455..ed471988 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -240,9 +240,9 @@ where |tree, renderer, layout, element| { element.as_widget().draw( tree, renderer, theme, style, layout, cursor, viewport, - ) + ); }, - ) + ); } fn mouse_interaction( diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index f868a648..3fb25972 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -308,7 +308,7 @@ where .zip(layout.children()) .for_each(|(((_pane, content), state), layout)| { content.operate(state, layout, renderer, operation); - }) + }); }); } @@ -436,7 +436,7 @@ where tree, renderer, theme, style, layout, cursor, rectangle, ); }, - ) + ); } fn overlay<'b>( diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index 28a52cf0..3721fa55 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -156,16 +156,16 @@ impl State { Region::Center => self.swap(pane, target), Region::Edge(edge) => match edge { Edge::Top => { - self.split_and_swap(Axis::Horizontal, target, pane, true) + self.split_and_swap(Axis::Horizontal, target, pane, true); } Edge::Bottom => { - self.split_and_swap(Axis::Horizontal, target, pane, false) + self.split_and_swap(Axis::Horizontal, target, pane, false); } Edge::Left => { - self.split_and_swap(Axis::Vertical, target, pane, true) + self.split_and_swap(Axis::Vertical, target, pane, true); } Edge::Right => { - self.split_and_swap(Axis::Vertical, target, pane, false) + self.split_and_swap(Axis::Vertical, target, pane, false); } }, } @@ -176,7 +176,7 @@ impl State { match target { Target::Edge(edge) => self.move_to_edge(pane, edge), Target::Pane(target, region) => { - self.split_with(&target, pane, region) + self.split_with(&target, pane, region); } } } @@ -241,16 +241,16 @@ impl State { pub fn move_to_edge(&mut self, pane: &Pane, edge: Edge) { match edge { Edge::Top => { - self.split_major_node_and_swap(Axis::Horizontal, pane, true) + self.split_major_node_and_swap(Axis::Horizontal, pane, true); } Edge::Bottom => { - self.split_major_node_and_swap(Axis::Horizontal, pane, false) + self.split_major_node_and_swap(Axis::Horizontal, pane, false); } Edge::Left => { - self.split_major_node_and_swap(Axis::Vertical, pane, true) + self.split_major_node_and_swap(Axis::Vertical, pane, true); } Edge::Right => { - self.split_major_node_and_swap(Axis::Vertical, pane, false) + self.split_major_node_and_swap(Axis::Vertical, pane, false); } } } diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 5ae7a6a0..f4dbb6b1 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -286,7 +286,7 @@ where controls_layout, renderer, operation, - ) + ); }; if show_title { @@ -295,7 +295,7 @@ where title_layout, renderer, operation, - ) + ); } } diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 056a5e65..fa0e3471 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -253,7 +253,7 @@ where &self.handle, &self.style, || tree.state.downcast_ref::>(), - ) + ); } fn overlay<'b>( diff --git a/widget/src/row.rs b/widget/src/row.rs index 71cf0509..7ca90fbb 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -101,7 +101,7 @@ where } fn diff(&self, tree: &mut Tree) { - tree.diff_children(&self.children) + tree.diff_children(&self.children); } fn width(&self) -> Length { @@ -148,7 +148,7 @@ where child .as_widget() .operate(state, layout, renderer, operation); - }) + }); }); } diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index f92e6223..6f1e68fc 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -217,7 +217,7 @@ where } fn diff(&self, tree: &mut Tree) { - tree.diff_children(std::slice::from_ref(&self.content)) + tree.diff_children(std::slice::from_ref(&self.content)); } fn width(&self) -> Length { @@ -348,9 +348,9 @@ where layout, cursor, viewport, - ) + ); }, - ) + ); } fn mouse_interaction( @@ -1069,7 +1069,7 @@ impl operation::Scrollable for State { } fn scroll_to(&mut self, offset: AbsoluteOffset) { - State::scroll_to(self, offset) + State::scroll_to(self, offset); } } @@ -1203,7 +1203,7 @@ impl State { (self.offset_y.absolute(bounds.height, content_bounds.height) - delta.y) .clamp(0.0, content_bounds.height - bounds.height), - ) + ); } if bounds.width < content_bounds.width { diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 2c4a2913..ac0982c8 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -223,7 +223,7 @@ where &self.range, theme, &self.style, - ) + ); } fn mouse_interaction( diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 7d5ae806..9e1fb796 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -250,7 +250,7 @@ where self.is_secure, self.icon.as_ref(), &self.style, - ) + ); } } @@ -375,7 +375,7 @@ where self.is_secure, self.icon.as_ref(), &self.style, - ) + ); } fn mouse_interaction( @@ -622,7 +622,7 @@ where font, size, line_height, - ) + ); }; match event { @@ -849,7 +849,7 @@ where state.cursor.move_left_by_words(value); } } else if modifiers.shift() { - state.cursor.select_left(value) + state.cursor.select_left(value); } else { state.cursor.move_left(value); } @@ -864,7 +864,7 @@ where state.cursor.move_right_by_words(value); } } else if modifiers.shift() { - state.cursor.select_right(value) + state.cursor.select_right(value); } else { state.cursor.move_right(value); } @@ -1220,7 +1220,7 @@ pub fn draw( if text_width > text_bounds.width { renderer.with_layer(text_bounds, |renderer| { - renderer.with_translation(Vector::new(-offset, 0.0), render) + renderer.with_translation(Vector::new(-offset, 0.0), render); }); } else { render(renderer); @@ -1342,29 +1342,29 @@ impl operation::Focusable for State

{ } fn focus(&mut self) { - State::focus(self) + State::focus(self); } fn unfocus(&mut self) { - State::unfocus(self) + State::unfocus(self); } } impl operation::TextInput for State

{ fn move_cursor_to_front(&mut self) { - State::move_cursor_to_front(self) + State::move_cursor_to_front(self); } fn move_cursor_to_end(&mut self) { - State::move_cursor_to_end(self) + State::move_cursor_to_end(self); } fn move_cursor_to(&mut self, position: usize) { - State::move_cursor_to(self, position) + State::move_cursor_to(self, position); } fn select_all(&mut self) { - State::select_all(self) + State::select_all(self); } } diff --git a/widget/src/text_input/cursor.rs b/widget/src/text_input/cursor.rs index 9680dfd7..ea902485 100644 --- a/widget/src/text_input/cursor.rs +++ b/widget/src/text_input/cursor.rs @@ -65,11 +65,11 @@ impl Cursor { } pub(crate) fn move_right(&mut self, value: &Value) { - self.move_right_by_amount(value, 1) + self.move_right_by_amount(value, 1); } pub(crate) fn move_right_by_words(&mut self, value: &Value) { - self.move_to(value.next_end_of_word(self.right(value))) + self.move_to(value.next_end_of_word(self.right(value))); } pub(crate) fn move_right_by_amount( @@ -79,7 +79,7 @@ impl Cursor { ) { match self.state(value) { State::Index(index) => { - self.move_to(index.saturating_add(amount).min(value.len())) + self.move_to(index.saturating_add(amount).min(value.len())); } State::Selection { start, end } => self.move_to(end.max(start)), } @@ -108,10 +108,10 @@ impl Cursor { pub(crate) fn select_left(&mut self, value: &Value) { match self.state(value) { State::Index(index) if index > 0 => { - self.select_range(index, index - 1) + self.select_range(index, index - 1); } State::Selection { start, end } if end > 0 => { - self.select_range(start, end - 1) + self.select_range(start, end - 1); } _ => {} } @@ -120,10 +120,10 @@ impl Cursor { pub(crate) fn select_right(&mut self, value: &Value) { match self.state(value) { State::Index(index) if index < value.len() => { - self.select_range(index, index + 1) + self.select_range(index, index + 1); } State::Selection { start, end } if end < value.len() => { - self.select_range(start, end + 1) + self.select_range(start, end + 1); } _ => {} } @@ -132,10 +132,10 @@ impl Cursor { pub(crate) fn select_left_by_words(&mut self, value: &Value) { match self.state(value) { State::Index(index) => { - self.select_range(index, value.previous_start_of_word(index)) + self.select_range(index, value.previous_start_of_word(index)); } State::Selection { start, end } => { - self.select_range(start, value.previous_start_of_word(end)) + self.select_range(start, value.previous_start_of_word(end)); } } } @@ -143,10 +143,10 @@ impl Cursor { pub(crate) fn select_right_by_words(&mut self, value: &Value) { match self.state(value) { State::Index(index) => { - self.select_range(index, value.next_end_of_word(index)) + self.select_range(index, value.next_end_of_word(index)); } State::Selection { start, end } => { - self.select_range(start, value.next_end_of_word(end)) + self.select_range(start, value.next_end_of_word(end)); } } } diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index edc74e31..b041d2e9 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -114,7 +114,7 @@ where } fn diff(&self, tree: &mut widget::Tree) { - tree.diff_children(&[self.content.as_widget(), &self.tooltip]) + tree.diff_children(&[self.content.as_widget(), &self.tooltip]); } fn state(&self) -> widget::tree::State { diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 1efcd63b..01d3359c 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -220,7 +220,7 @@ where &self.range, theme, &self.style, - ) + ); } fn mouse_interaction( diff --git a/winit/src/application.rs b/winit/src/application.rs index dec77e80..8105f8d9 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -762,7 +762,7 @@ pub fn run_command( size.width, size.height, ))) - .expect("Send message to event loop") + .expect("Send message to event loop"); } window::Action::Maximize(maximized) => { window.set_maximized(maximized); @@ -784,7 +784,7 @@ pub fn run_command( )); } window::Action::ChangeIcon(icon) => { - window.set_window_icon(conversion::icon(icon)) + window.set_window_icon(conversion::icon(icon)); } window::Action::FetchMode(tag) => { let mode = if window.is_visible().unwrap_or(true) { @@ -798,7 +798,7 @@ pub fn run_command( .expect("Send message to event loop"); } window::Action::ToggleMaximize => { - window.set_maximized(!window.is_maximized()) + window.set_maximized(!window.is_maximized()); } window::Action::ToggleDecorations => { window.set_decorations(!window.is_decorated()); @@ -833,7 +833,7 @@ pub fn run_command( bytes, state.physical_size(), ))) - .expect("Send message to event loop.") + .expect("Send message to event loop."); } }, command::Action::System(action) => match action { @@ -851,7 +851,7 @@ pub fn run_command( proxy .send_event(message) - .expect("Send message to event loop") + .expect("Send message to event loop"); }); } } diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 4228e46f..0a09c255 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -44,7 +44,7 @@ impl Clipboard { State::Connected(clipboard) => match clipboard.write(contents) { Ok(()) => {} Err(error) => { - log::warn!("error writing to clipboard: {error}") + log::warn!("error writing to clipboard: {error}"); } }, State::Unavailable => {} @@ -58,6 +58,6 @@ impl crate::core::Clipboard for Clipboard { } fn write(&mut self, contents: String) { - self.write(contents) + self.write(contents); } } -- cgit From 6c386e90a12fd26da12541da3f086dddb7211c0c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 04:33:48 +0200 Subject: Fix `clippy::trivially-copy-pass-by-ref` --- .cargo/config.toml | 2 +- core/src/mouse/click.rs | 2 +- examples/pane_grid/src/main.rs | 29 +++++++++-------------- examples/todos/src/main.rs | 2 +- widget/src/pane_grid/node.rs | 14 +++++------ widget/src/pane_grid/state.rs | 54 +++++++++++++++++++++--------------------- 6 files changed, 48 insertions(+), 55 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 08884888..1e55e447 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,2 @@ [alias] -lint = "clippy --workspace --no-deps -- -D warnings -D clippy::semicolon_if_nothing_returned" +lint = "clippy --workspace --no-deps -- -D warnings -D clippy::semicolon_if_nothing_returned -D clippy::trivially-copy-pass-by-ref" diff --git a/core/src/mouse/click.rs b/core/src/mouse/click.rs index 240e5c64..53354098 100644 --- a/core/src/mouse/click.rs +++ b/core/src/mouse/click.rs @@ -24,7 +24,7 @@ pub enum Kind { } impl Kind { - fn next(&self) -> Kind { + fn next(self) -> Kind { match self { Kind::Single => Kind::Double, Kind::Double => Kind::Triple, diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index af87e2c0..aa3149bb 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -61,11 +61,8 @@ impl Application for Example { fn update(&mut self, message: Message) -> Command { match message { Message::Split(axis, pane) => { - let result = self.panes.split( - axis, - &pane, - Pane::new(self.panes_created), - ); + let result = + self.panes.split(axis, pane, Pane::new(self.panes_created)); if let Some((pane, _)) = result { self.focus = Some(pane); @@ -77,7 +74,7 @@ impl Application for Example { if let Some(pane) = self.focus { let result = self.panes.split( axis, - &pane, + pane, Pane::new(self.panes_created), ); @@ -90,8 +87,7 @@ impl Application for Example { } Message::FocusAdjacent(direction) => { if let Some(pane) = self.focus { - if let Some(adjacent) = - self.panes.adjacent(&pane, direction) + if let Some(adjacent) = self.panes.adjacent(pane, direction) { self.focus = Some(adjacent); } @@ -101,37 +97,34 @@ impl Application for Example { self.focus = Some(pane); } Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { - self.panes.resize(&split, ratio); + self.panes.resize(split, ratio); } Message::Dragged(pane_grid::DragEvent::Dropped { pane, target, }) => { - self.panes.drop(&pane, target); + self.panes.drop(pane, target); } Message::Dragged(_) => {} Message::TogglePin(pane) => { - if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(&pane) - { + if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(pane) { *is_pinned = !*is_pinned; } } - Message::Maximize(pane) => self.panes.maximize(&pane), + Message::Maximize(pane) => self.panes.maximize(pane), Message::Restore => { self.panes.restore(); } Message::Close(pane) => { - if let Some((_, sibling)) = self.panes.close(&pane) { + if let Some((_, sibling)) = self.panes.close(pane) { self.focus = Some(sibling); } } Message::CloseFocused => { if let Some(pane) = self.focus { - if let Some(Pane { is_pinned, .. }) = self.panes.get(&pane) - { + if let Some(Pane { is_pinned, .. }) = self.panes.get(pane) { if !is_pinned { - if let Some((_, sibling)) = self.panes.close(&pane) - { + if let Some((_, sibling)) = self.panes.close(pane) { self.focus = Some(sibling); } } diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 3048a668..1ad3aba7 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -443,7 +443,7 @@ pub enum Filter { } impl Filter { - fn matches(&self, task: &Task) -> bool { + fn matches(self, task: &Task) -> bool { match self { Filter::All => true, Filter::Active => !task.completed, diff --git a/widget/src/pane_grid/node.rs b/widget/src/pane_grid/node.rs index 3c707f15..1f568f95 100644 --- a/widget/src/pane_grid/node.rs +++ b/widget/src/pane_grid/node.rs @@ -95,13 +95,13 @@ impl Node { splits } - pub(crate) fn find(&mut self, pane: &Pane) -> Option<&mut Node> { + pub(crate) fn find(&mut self, pane: Pane) -> Option<&mut Node> { match self { Node::Split { a, b, .. } => { a.find(pane).or_else(move || b.find(pane)) } Node::Pane(p) => { - if p == pane { + if *p == pane { Some(self) } else { None @@ -139,12 +139,12 @@ impl Node { f(self); } - pub(crate) fn resize(&mut self, split: &Split, percentage: f32) -> bool { + pub(crate) fn resize(&mut self, split: Split, percentage: f32) -> bool { match self { Node::Split { id, ratio, a, b, .. } => { - if id == split { + if *id == split { *ratio = percentage; true @@ -158,13 +158,13 @@ impl Node { } } - pub(crate) fn remove(&mut self, pane: &Pane) -> Option { + pub(crate) fn remove(&mut self, pane: Pane) -> Option { match self { Node::Split { a, b, .. } => { - if a.pane() == Some(*pane) { + if a.pane() == Some(pane) { *self = *b.clone(); Some(self.first_pane()) - } else if b.pane() == Some(*pane) { + } else if b.pane() == Some(pane) { *self = *a.clone(); Some(self.first_pane()) } else { diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index 3721fa55..481cd770 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -75,14 +75,14 @@ impl State { } /// Returns the internal state of the given [`Pane`], if it exists. - pub fn get(&self, pane: &Pane) -> Option<&T> { - self.panes.get(pane) + pub fn get(&self, pane: Pane) -> Option<&T> { + self.panes.get(&pane) } /// Returns the internal state of the given [`Pane`] with mutability, if it /// exists. - pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> { - self.panes.get_mut(pane) + pub fn get_mut(&mut self, pane: Pane) -> Option<&mut T> { + self.panes.get_mut(&pane) } /// Returns an iterator over all the panes of the [`State`], alongside its @@ -104,13 +104,13 @@ impl State { /// Returns the adjacent [`Pane`] of another [`Pane`] in the given /// direction, if there is one. - pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option { + pub fn adjacent(&self, pane: Pane, direction: Direction) -> Option { let regions = self .internal .layout .pane_regions(0.0, Size::new(4096.0, 4096.0)); - let current_region = regions.get(pane)?; + let current_region = regions.get(&pane)?; let target = match direction { Direction::Left => { @@ -142,7 +142,7 @@ impl State { pub fn split( &mut self, axis: Axis, - pane: &Pane, + pane: Pane, state: T, ) -> Option<(Pane, Split)> { self.split_node(axis, Some(pane), state, false) @@ -151,7 +151,7 @@ impl State { /// Split a target [`Pane`] with a given [`Pane`] on a given [`Region`]. /// /// Panes will be swapped by default for [`Region::Center`]. - pub fn split_with(&mut self, target: &Pane, pane: &Pane, region: Region) { + pub fn split_with(&mut self, target: Pane, pane: Pane, region: Region) { match region { Region::Center => self.swap(pane, target), Region::Edge(edge) => match edge { @@ -172,11 +172,11 @@ impl State { } /// Drops the given [`Pane`] into the provided [`Target`]. - pub fn drop(&mut self, pane: &Pane, target: Target) { + pub fn drop(&mut self, pane: Pane, target: Target) { match target { Target::Edge(edge) => self.move_to_edge(pane, edge), Target::Pane(target, region) => { - self.split_with(&target, pane, region); + self.split_with(target, pane, region); } } } @@ -184,7 +184,7 @@ impl State { fn split_node( &mut self, axis: Axis, - pane: Option<&Pane>, + pane: Option, state: T, inverse: bool, ) -> Option<(Pane, Split)> { @@ -222,14 +222,14 @@ impl State { fn split_and_swap( &mut self, axis: Axis, - target: &Pane, - pane: &Pane, + target: Pane, + pane: Pane, swap: bool, ) { if let Some((state, _)) = self.close(pane) { if let Some((new_pane, _)) = self.split(axis, target, state) { if swap { - self.swap(target, &new_pane); + self.swap(target, new_pane); } } } @@ -238,7 +238,7 @@ impl State { /// Move [`Pane`] to an [`Edge`] of the [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid - pub fn move_to_edge(&mut self, pane: &Pane, edge: Edge) { + pub fn move_to_edge(&mut self, pane: Pane, edge: Edge) { match edge { Edge::Top => { self.split_major_node_and_swap(Axis::Horizontal, pane, true); @@ -258,7 +258,7 @@ impl State { fn split_major_node_and_swap( &mut self, axis: Axis, - pane: &Pane, + pane: Pane, swap: bool, ) { if let Some((state, _)) = self.close(pane) { @@ -273,14 +273,14 @@ impl State { /// /// [`PaneGrid`]: super::PaneGrid /// [`DragEvent`]: super::DragEvent - pub fn swap(&mut self, a: &Pane, b: &Pane) { + pub fn swap(&mut self, a: Pane, b: Pane) { self.internal.layout.update(&|node| match node { Node::Split { .. } => {} Node::Pane(pane) => { - if pane == a { - *node = Node::Pane(*b); - } else if pane == b { - *node = Node::Pane(*a); + if *pane == a { + *node = Node::Pane(b); + } else if *pane == b { + *node = Node::Pane(a); } } }); @@ -296,19 +296,19 @@ impl State { /// /// [`PaneGrid`]: super::PaneGrid /// [`ResizeEvent`]: super::ResizeEvent - pub fn resize(&mut self, split: &Split, ratio: f32) { + pub fn resize(&mut self, split: Split, ratio: f32) { let _ = self.internal.layout.resize(split, ratio); } /// Closes the given [`Pane`] and returns its internal state and its closest /// sibling, if it exists. - pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> { - if self.maximized == Some(*pane) { + pub fn close(&mut self, pane: Pane) -> Option<(T, Pane)> { + if self.maximized == Some(pane) { let _ = self.maximized.take(); } if let Some(sibling) = self.internal.layout.remove(pane) { - self.panes.remove(pane).map(|state| (state, sibling)) + self.panes.remove(&pane).map(|state| (state, sibling)) } else { None } @@ -318,8 +318,8 @@ impl State { /// [`PaneGrid`] until [`Self::restore()`] is called. /// /// [`PaneGrid`]: super::PaneGrid - pub fn maximize(&mut self, pane: &Pane) { - self.maximized = Some(*pane); + pub fn maximize(&mut self, pane: Pane) { + self.maximized = Some(pane); } /// Restore the currently maximized [`Pane`] to it's normal size. All panes -- cgit From 42ed90bc6f92b2085d193e7f143430b8d3847c21 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 04:51:08 +0200 Subject: Fix `clippy::default_trait_access` --- .cargo/config.toml | 2 +- examples/arc/src/main.rs | 2 +- examples/clock/src/main.rs | 2 +- examples/integration/src/controls.rs | 2 +- examples/lazy/src/main.rs | 4 ++-- examples/modal/src/main.rs | 6 ++++-- examples/scrollable/src/main.rs | 4 ++-- examples/solar_system/src/main.rs | 4 ++-- renderer/src/geometry/cache.rs | 2 +- src/settings.rs | 4 ++-- src/window/settings.rs | 2 +- style/src/theme.rs | 6 +++--- tiny_skia/src/geometry.rs | 2 +- wgpu/src/color.rs | 6 +++--- wgpu/src/text.rs | 2 +- widget/src/pick_list.rs | 2 +- widget/src/scrollable.rs | 2 +- widget/src/toggler.rs | 2 +- winit/src/settings.rs | 2 +- 19 files changed, 30 insertions(+), 28 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 1e55e447..f5c00d9b 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,2 @@ [alias] -lint = "clippy --workspace --no-deps -- -D warnings -D clippy::semicolon_if_nothing_returned -D clippy::trivially-copy-pass-by-ref" +lint = "clippy --workspace --no-deps -- -D warnings -D clippy::semicolon_if_nothing_returned -D clippy::trivially-copy-pass-by-ref -D clippy::default_trait_access" diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs index df565859..6a68cca1 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -37,7 +37,7 @@ impl Application for Arc { ( Arc { start: Instant::now(), - cache: Default::default(), + cache: Cache::default(), }, Command::none(), ) diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index eec70dde..920aa0c5 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -35,7 +35,7 @@ impl Application for Clock { Clock { now: time::OffsetDateTime::now_local() .unwrap_or_else(|_| time::OffsetDateTime::now_utc()), - clock: Default::default(), + clock: Cache::default(), }, Command::none(), ) diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index 14e53ede..4714c397 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -19,7 +19,7 @@ impl Controls { pub fn new() -> Controls { Controls { background_color: Color::BLACK, - text: Default::default(), + text: String::default(), } } diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs index c6baa6a1..9bf17c56 100644 --- a/examples/lazy/src/main.rs +++ b/examples/lazy/src/main.rs @@ -27,7 +27,7 @@ impl Default for App { .into_iter() .map(From::from) .collect(), - input: Default::default(), + input: String::default(), order: Order::Ascending, } } @@ -107,7 +107,7 @@ impl From<&str> for Item { fn from(s: &str) -> Self { Self { name: s.to_owned(), - color: Default::default(), + color: Color::default(), } } } diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index c050d3cc..b0e2c81b 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -228,7 +228,9 @@ mod modal { use iced::alignment::Alignment; use iced::event; use iced::mouse; - use iced::{Color, Element, Event, Length, Point, Rectangle, Size}; + use iced::{ + BorderRadius, Color, Element, Event, Length, Point, Rectangle, Size, + }; /// A widget that centers a modal element over some base element pub struct Modal<'a, Message, Renderer> { @@ -474,7 +476,7 @@ mod modal { renderer.fill_quad( renderer::Quad { bounds: layout.bounds(), - border_radius: Default::default(), + border_radius: BorderRadius::default(), border_width: 0.0, border_color: Color::TRANSPARENT, }, diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index 21e69284..d82ea841 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -391,12 +391,12 @@ impl scrollable::StyleSheet for ScrollbarCustomStyle { .background, border_radius: 2.0.into(), border_width: 0.0, - border_color: Default::default(), + border_color: Color::default(), scroller: Scroller { color: Color::from_rgb8(250, 85, 134), border_radius: 2.0.into(), border_width: 0.0, - border_color: Default::default(), + border_color: Color::default(), }, } } else { diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 8fa8946e..8295dded 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -117,8 +117,8 @@ impl State { let (width, height) = window::Settings::default().size; State { - space_cache: Default::default(), - system_cache: Default::default(), + space_cache: canvas::Cache::default(), + system_cache: canvas::Cache::default(), start: now, now, stars: Self::generate_stars(width, height), diff --git a/renderer/src/geometry/cache.rs b/renderer/src/geometry/cache.rs index d82e7f69..d4bb04b3 100644 --- a/renderer/src/geometry/cache.rs +++ b/renderer/src/geometry/cache.rs @@ -35,7 +35,7 @@ impl Cache { /// Creates a new empty [`Cache`]. pub fn new() -> Self { Cache { - state: Default::default(), + state: RefCell::default(), } } diff --git a/src/settings.rs b/src/settings.rs index d9778d7e..c5e28e86 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -77,9 +77,9 @@ where fn default() -> Self { Self { id: None, - window: Default::default(), + window: window::Settings::default(), flags: Default::default(), - default_font: Default::default(), + default_font: Font::default(), default_text_size: Pixels(16.0), antialiasing: false, exit_on_close_request: true, diff --git a/src/window/settings.rs b/src/window/settings.rs index 458b9232..0ee573e5 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -52,7 +52,7 @@ impl Default for Settings { transparent: false, level: Level::default(), icon: None, - platform_specific: Default::default(), + platform_specific: PlatformSpecific::default(), } } } diff --git a/style/src/theme.rs b/style/src/theme.rs index 893d7202..3c1f2de6 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -393,7 +393,7 @@ impl container::StyleSheet for Theme { fn appearance(&self, style: &Self::Style) -> container::Appearance { match style { - Container::Transparent => Default::default(), + Container::Transparent => container::Appearance::default(), Container::Box => { let palette = self.extended_palette(); @@ -904,7 +904,7 @@ impl svg::StyleSheet for Theme { fn appearance(&self, style: &Self::Style) -> svg::Appearance { match style { - Svg::Default => Default::default(), + Svg::Default => svg::Appearance::default(), Svg::Custom(custom) => custom.appearance(self), } } @@ -1053,7 +1053,7 @@ impl text::StyleSheet for Theme { fn appearance(&self, style: Self::Style) -> text::Appearance { match style { - Text::Default => Default::default(), + Text::Default => text::Appearance::default(), Text::Color(c) => text::Appearance { color: Some(c) }, } } diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 1d573f6a..1d14aa03 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -180,7 +180,7 @@ fn convert_path(path: &Path) -> Option { use iced_graphics::geometry::path::lyon_path; let mut builder = tiny_skia::PathBuilder::new(); - let mut last_point = Default::default(); + let mut last_point = lyon_path::math::Point::default(); for event in path.raw() { match event { diff --git a/wgpu/src/color.rs b/wgpu/src/color.rs index a1025601..20827e3c 100644 --- a/wgpu/src/color.rs +++ b/wgpu/src/color.rs @@ -12,7 +12,7 @@ pub fn convert( let sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("iced_wgpu.offscreen.sampler"), - ..Default::default() + ..wgpu::SamplerDescriptor::default() }); //sampler in 0 @@ -102,10 +102,10 @@ pub fn convert( primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, front_face: wgpu::FrontFace::Cw, - ..Default::default() + ..wgpu::PrimitiveState::default() }, depth_stencil: None, - multisample: Default::default(), + multisample: wgpu::MultisampleState::default(), multiview: None, }); diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index bd4f3e06..2a530cad 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -64,7 +64,7 @@ impl Pipeline { self.renderers.push(glyphon::TextRenderer::new( &mut self.atlas, device, - Default::default(), + wgpu::MultisampleState::default(), None, )); } diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index fa0e3471..27f32907 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -76,7 +76,7 @@ where text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Basic, font: None, - handle: Default::default(), + handle: Handle::default(), style: Default::default(), } } diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 6f1e68fc..4cc97684 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -46,7 +46,7 @@ where id: None, width: Length::Shrink, height: Length::Shrink, - direction: Default::default(), + direction: Direction::default(), content: content.into(), on_scroll: None, style: Default::default(), diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 2440317f..476c8330 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -286,7 +286,7 @@ where style, label_layout, tree.state.downcast_ref(), - Default::default(), + crate::text::Appearance::default(), ); } diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 8d3e1b47..867dad0f 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -235,7 +235,7 @@ impl Default for Window { transparent: false, level: Level::default(), icon: None, - platform_specific: Default::default(), + platform_specific: PlatformSpecific::default(), } } } -- cgit From caed50b277495e4375975f3f4e271b8fcbc0c33f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 05:03:25 +0200 Subject: Fix `clippy::match-wildcard-for-single-variants` --- .cargo/config.toml | 32 +++++++++++++++++++++++++++++++- examples/download_progress/src/main.rs | 2 +- examples/game_of_life/src/main.rs | 4 ++-- renderer/src/lib.rs | 4 ++-- wgpu/src/image/atlas.rs | 2 +- widget/src/scrollable.rs | 4 ++-- widget/src/text_input/cursor.rs | 4 ++-- 7 files changed, 41 insertions(+), 11 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index f5c00d9b..83564651 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,32 @@ [alias] -lint = "clippy --workspace --no-deps -- -D warnings -D clippy::semicolon_if_nothing_returned -D clippy::trivially-copy-pass-by-ref -D clippy::default_trait_access" +lint = """ +clippy --workspace --no-deps -- \ + -D warnings \ + -D clippy::semicolon_if_nothing_returned \ + -D clippy::trivially-copy-pass-by-ref \ + -D clippy::default_trait_access \ + -D clippy::match-wildcard-for-single-variants +""" + +nitpick = """ +clippy --workspace --no-deps -- \ + -D warnings \ + -D clippy::pedantic \ + -A clippy::must_use_candidate \ + -A clippy::return_self_not_must_use \ + -A clippy::needless_pass_by_value \ + -A clippy::cast_precision_loss \ + -A clippy::cast_sign_loss \ + -A clippy::cast_possible_truncation \ + -A clippy::match_same_arms \ + -A clippy::missing-errors-doc \ + -A clippy::missing-panics-doc \ + -A clippy::cast_lossless \ + -A clippy::doc_markdown \ + -A clippy::items_after_statements \ + -A clippy::too_many_lines \ + -A clippy::module_name_repetitions \ + -A clippy::if_not_else \ + -A clippy::redundant_else \ + -A clippy::used_underscore_binding +""" diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index 001a1f8f..e52c604c 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -123,7 +123,7 @@ impl Download { | State::Errored { .. } => { self.state = State::Downloading { progress: 0.0 }; } - _ => {} + State::Downloading{ .. } => {} } } diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 437d89d5..c774e769 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -472,7 +472,7 @@ mod grid { * (1.0 / self.scaling), )) } - _ => None, + Interaction::None => None, }; let event_status = match interaction { @@ -676,7 +676,7 @@ mod grid { Interaction::None if cursor.is_over(bounds) => { mouse::Interaction::Crosshair } - _ => mouse::Interaction::default(), + Interaction::None => mouse::Interaction::default(), } } } diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 8c76f52e..1347ce04 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -257,7 +257,7 @@ impl crate::graphics::geometry::Renderer for Renderer { crate::Geometry::TinySkia(primitive) => { renderer.draw_primitive(primitive); } - _ => unreachable!(), + crate::Geometry::Wgpu(_) => unreachable!(), } } } @@ -268,7 +268,7 @@ impl crate::graphics::geometry::Renderer for Renderer { crate::Geometry::Wgpu(primitive) => { renderer.draw_primitive(primitive); } - _ => unreachable!(), + crate::Geometry::TinySkia(_) => unreachable!(), } } } diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index e8ca4bd3..789e35b4 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -237,7 +237,7 @@ impl Atlas { })); } } - _ => {} + Layer::Full => {} } } diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 4cc97684..49aed2f0 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -117,7 +117,7 @@ impl Direction { match self { Self::Horizontal(properties) => Some(properties), Self::Both { horizontal, .. } => Some(horizontal), - _ => None, + Self::Vertical(_) => None, } } @@ -126,7 +126,7 @@ impl Direction { match self { Self::Vertical(properties) => Some(properties), Self::Both { vertical, .. } => Some(vertical), - _ => None, + Self::Horizontal(_) => None, } } } diff --git a/widget/src/text_input/cursor.rs b/widget/src/text_input/cursor.rs index ea902485..f682b17d 100644 --- a/widget/src/text_input/cursor.rs +++ b/widget/src/text_input/cursor.rs @@ -56,7 +56,7 @@ impl Cursor { State::Selection { start, end } => { Some((start.min(end), start.max(end))) } - _ => None, + State::Index(_) => None, } } @@ -89,7 +89,7 @@ impl Cursor { match self.state(value) { State::Index(index) if index > 0 => self.move_to(index - 1), State::Selection { start, end } => self.move_to(start.min(end)), - _ => self.move_to(0), + State::Index(_) => self.move_to(0), } } -- cgit From 1e4bade53aaaeb17542d0372ac827bcb7daf037c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 05:07:34 +0200 Subject: Fix `clippy::redundant-closure-for-method-calls` --- .cargo/config.toml | 6 ++++-- widget/src/lazy/component.rs | 2 +- winit/src/system.rs | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 83564651..61bfbf17 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,7 +5,8 @@ clippy --workspace --no-deps -- \ -D clippy::semicolon_if_nothing_returned \ -D clippy::trivially-copy-pass-by-ref \ -D clippy::default_trait_access \ - -D clippy::match-wildcard-for-single-variants + -D clippy::match-wildcard-for-single-variants \ + -D clippy::redundant-closure-for-method-calls """ nitpick = """ @@ -28,5 +29,6 @@ clippy --workspace --no-deps -- \ -A clippy::module_name_repetitions \ -A clippy::if_not_else \ -A clippy::redundant_else \ - -A clippy::used_underscore_binding + -A clippy::used_underscore_binding \ + -A clippy::cast_possible_wrap """ diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index fe99a7f2..d454b72b 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -511,7 +511,7 @@ impl<'a, 'b, Message, Renderer, Event, S> Drop for Overlay<'a, 'b, Message, Renderer, Event, S> { fn drop(&mut self) { - if let Some(heads) = self.0.take().map(|inner| inner.into_heads()) { + if let Some(heads) = self.0.take().map(Inner::into_heads) { *heads.instance.tree.borrow_mut().borrow_mut() = Some(heads.tree); } } diff --git a/winit/src/system.rs b/winit/src/system.rs index 145a4d92..d4cef60e 100644 --- a/winit/src/system.rs +++ b/winit/src/system.rs @@ -23,7 +23,7 @@ pub(crate) fn information( let memory_used = sysinfo::get_current_pid() .and_then(|pid| system.process(pid).ok_or("Process not found")) - .map(|process| process.memory()) + .map(ProcessExt::memory) .ok(); Information { -- cgit From 14ba939e674ec4d9ca53b506ffa3259d30216e85 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 05:19:24 +0200 Subject: Fix `clippy::unreadable_literal` --- .cargo/config.toml | 9 +++++++-- tiny_skia/src/vector.rs | 6 +++--- wgpu/src/triangle.rs | 10 +++++----- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 61bfbf17..4ae09897 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,7 +6,7 @@ clippy --workspace --no-deps -- \ -D clippy::trivially-copy-pass-by-ref \ -D clippy::default_trait_access \ -D clippy::match-wildcard-for-single-variants \ - -D clippy::redundant-closure-for-method-calls + -D clippy::redundant-closure-for-method-calls \ """ nitpick = """ @@ -30,5 +30,10 @@ clippy --workspace --no-deps -- \ -A clippy::if_not_else \ -A clippy::redundant_else \ -A clippy::used_underscore_binding \ - -A clippy::cast_possible_wrap + -A clippy::cast_possible_wrap \ + -A clippy::unnecessary_wraps \ + -A clippy::struct-excessive-bools \ + -A clippy::float-cmp \ + -A clippy::single_match_else \ + -A clippy::unreadable_literal """ diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index 490b9f69..a1cd269d 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -172,9 +172,9 @@ impl Cache { for pixel in bytemuck::cast_slice_mut::(image.data_mut()) { - *pixel = *pixel & 0xFF00FF00 - | ((0x000000FF & *pixel) << 16) - | ((0x00FF0000 & *pixel) >> 16); + *pixel = *pixel & 0xFF00_FF00 + | ((0x0000_00FF & *pixel) << 16) + | ((0x00FF_0000 & *pixel) >> 16); } } diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 7e1bd9cc..f8014ceb 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -329,12 +329,12 @@ impl Pipeline { fn fragment_target( texture_format: wgpu::TextureFormat, -) -> Option { - Some(wgpu::ColorTargetState { +) -> wgpu::ColorTargetState { + wgpu::ColorTargetState { format: texture_format, blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, - }) + } } fn primitive_state() -> wgpu::PrimitiveState { @@ -521,7 +521,7 @@ mod solid { fragment: Some(wgpu::FragmentState { module: &shader, entry_point: "solid_fs_main", - targets: &[triangle::fragment_target(format)], + targets: &[Some(triangle::fragment_target(format))], }), primitive: triangle::primitive_state(), depth_stencil: None, @@ -698,7 +698,7 @@ mod gradient { fragment: Some(wgpu::FragmentState { module: &shader, entry_point: "gradient_fs_main", - targets: &[triangle::fragment_target(format)], + targets: &[Some(triangle::fragment_target(format))], }), primitive: triangle::primitive_state(), depth_stencil: None, -- cgit From 1019d1e518d8ffe760142ccd5ff33d077434c8b9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 05:23:15 +0200 Subject: Fix `clippy::filter_map_next` --- .cargo/config.toml | 5 ++++- widget/src/pane_grid.rs | 25 +++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 4ae09897..d49e034f 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,6 +7,7 @@ clippy --workspace --no-deps -- \ -D clippy::default_trait_access \ -D clippy::match-wildcard-for-single-variants \ -D clippy::redundant-closure-for-method-calls \ + -D clippy::filter_map_next """ nitpick = """ @@ -35,5 +36,7 @@ clippy --workspace --no-deps -- \ -A clippy::struct-excessive-bools \ -A clippy::float-cmp \ -A clippy::single_match_else \ - -A clippy::unreadable_literal + -A clippy::unreadable_literal \ + -A clippy::explicit_deref_methods \ + -A clippy::map_unwrap_or """ diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 3fb25972..2d25a543 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -606,11 +606,10 @@ pub fn update<'a, Message, T: Draggable>( } else { let dropped_region = contents .zip(layout.children()) - .filter_map(|(target, layout)| { + .find_map(|(target, layout)| { layout_region(layout, cursor_position) .map(|region| (target, region)) - }) - .next(); + }); match dropped_region { Some(((target, _), region)) @@ -1151,21 +1150,19 @@ pub struct ResizeEvent { * Helpers */ fn hovered_split<'a>( - splits: impl Iterator, + mut splits: impl Iterator, spacing: f32, cursor_position: Point, ) -> Option<(Split, Axis, Rectangle)> { - splits - .filter_map(|(split, (axis, region, ratio))| { - let bounds = axis.split_line_bounds(*region, *ratio, spacing); + splits.find_map(|(split, (axis, region, ratio))| { + let bounds = axis.split_line_bounds(*region, *ratio, spacing); - if bounds.contains(cursor_position) { - Some((*split, *axis, bounds)) - } else { - None - } - }) - .next() + if bounds.contains(cursor_position) { + Some((*split, *axis, bounds)) + } else { + None + } + }) } /// The visible contents of the [`PaneGrid`] -- cgit From f8f1a8634402a5eb4275ff0814d03a3104fea65a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 05:30:08 +0200 Subject: Fix `clippy::manual_let_else` --- .cargo/config.toml | 8 ++++++-- examples/bezier_tool/src/main.rs | 9 +++------ examples/game_of_life/src/main.rs | 9 +++------ examples/integration/src/main.rs | 2 +- examples/integration/src/scene.rs | 1 - examples/sierpinski_triangle/src/main.rs | 5 +---- examples/websocket/src/echo/server.rs | 5 +---- 7 files changed, 15 insertions(+), 24 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index d49e034f..2c6b20b6 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,7 +7,8 @@ clippy --workspace --no-deps -- \ -D clippy::default_trait_access \ -D clippy::match-wildcard-for-single-variants \ -D clippy::redundant-closure-for-method-calls \ - -D clippy::filter_map_next + -D clippy::filter_map_next \ + -D clippy::manual_let_else """ nitpick = """ @@ -38,5 +39,8 @@ clippy --workspace --no-deps -- \ -A clippy::single_match_else \ -A clippy::unreadable_literal \ -A clippy::explicit_deref_methods \ - -A clippy::map_unwrap_or + -A clippy::map_unwrap_or \ + -A clippy::unnested_or_patterns \ + -A clippy::similar_names \ + -A clippy::unused_self """ diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 9e4bc49c..56cb23ba 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -100,12 +100,9 @@ mod bezier { bounds: Rectangle, cursor: mouse::Cursor, ) -> (event::Status, Option) { - let cursor_position = - if let Some(position) = cursor.position_in(bounds) { - position - } else { - return (event::Status::Ignored, None); - }; + let Some(cursor_position) = cursor.position_in(bounds) else { + return (event::Status::Ignored, None); + }; match event { Event::Mouse(mouse_event) => { diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index c774e769..96840143 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -406,12 +406,9 @@ mod grid { *interaction = Interaction::None; } - let cursor_position = - if let Some(position) = cursor.position_in(bounds) { - position - } else { - return (event::Status::Ignored, None); - }; + let Some(cursor_position) = cursor.position_in(bounds) else { + return (event::Status::Ignored, None); + }; let cell = Cell::at(self.project(cursor_position, bounds.size())); let is_populated = self.state.contains(&cell); diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 243297b2..4415fefa 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -256,7 +256,7 @@ pub fn main() -> Result<(), Box> { { // We clear the frame - let mut render_pass = scene.clear( + let mut render_pass = Scene::clear( &view, &mut encoder, program.background_color(), diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs index 90c7efbf..01808f40 100644 --- a/examples/integration/src/scene.rs +++ b/examples/integration/src/scene.rs @@ -16,7 +16,6 @@ impl Scene { } pub fn clear<'a>( - &self, target: &'a wgpu::TextureView, encoder: &'a mut wgpu::CommandEncoder, background_color: Color, diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index 885d3c63..ef935c33 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -108,10 +108,7 @@ impl canvas::Program for SierpinskiGraph { bounds: Rectangle, cursor: mouse::Cursor, ) -> (event::Status, Option) { - let cursor_position = if let Some(position) = cursor.position_in(bounds) - { - position - } else { + let Some(cursor_position) = cursor.position_in(bounds) else { return (event::Status::Ignored, None); }; diff --git a/examples/websocket/src/echo/server.rs b/examples/websocket/src/echo/server.rs index 168a635e..a696a7a4 100644 --- a/examples/websocket/src/echo/server.rs +++ b/examples/websocket/src/echo/server.rs @@ -47,10 +47,7 @@ async fn user_connected(ws: WebSocket) { }); while let Some(result) = user_ws_rx.next().await { - let msg = match result { - Ok(msg) => msg, - Err(_) => break, - }; + let Ok(msg) = result else { break }; let _ = tx.send(msg).await; } -- cgit From 432d9f5f97a7312878f2f86ead13b6742638f7e8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 05:36:11 +0200 Subject: Fix `clippy::unused_async` --- .cargo/config.toml | 3 ++- examples/screenshot/Cargo.toml | 8 ++++++-- examples/screenshot/src/main.rs | 23 ++++++++++++++--------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 2c6b20b6..9e265aa9 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -8,7 +8,8 @@ clippy --workspace --no-deps -- \ -D clippy::match-wildcard-for-single-variants \ -D clippy::redundant-closure-for-method-calls \ -D clippy::filter_map_next \ - -D clippy::manual_let_else + -D clippy::manual_let_else \ + -D clippy::unused_async """ nitpick = """ diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index dcd77439..77b108bd 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -7,7 +7,11 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["debug", "image", "advanced"] +iced.features = ["debug", "image", "advanced", "tokio"] + +image.workspace = true +image.features = ["png"] + +tokio.workspace = true -image = { workspace = true, features = ["png"]} tracing-subscriber = "0.3" \ No newline at end of file diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index d9784dc8..fa06d3e9 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -273,15 +273,20 @@ impl Application for Example { async fn save_to_png(screenshot: Screenshot) -> Result { let path = "screenshot.png".to_string(); - img::save_buffer( - &path, - &screenshot.bytes, - screenshot.size.width, - screenshot.size.height, - ColorType::Rgba8, - ) - .map(|_| path) - .map_err(|err| PngError(format!("{err:?}"))) + + tokio::task::spawn_blocking(move || { + img::save_buffer( + &path, + &screenshot.bytes, + screenshot.size.width, + screenshot.size.height, + ColorType::Rgba8, + ) + .map(|_| path) + .map_err(|err| PngError(format!("{err:?}"))) + }) + .await + .expect("Blocking task to finish") } #[derive(Clone, Debug)] -- cgit From 33d780f691829ecd32f3a218008fcb40e005deb4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 05:37:20 +0200 Subject: Run `cargo fmt` --- examples/download_progress/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index e52c604c..a2fcb275 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -123,7 +123,7 @@ impl Download { | State::Errored { .. } => { self.state = State::Downloading { progress: 0.0 }; } - State::Downloading{ .. } => {} + State::Downloading { .. } => {} } } -- cgit From 76873921af3cd04ac0cbed01ebbd66936454dbe3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 15:56:05 +0200 Subject: Fix `build` workflow in GitHub CI --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b30b7a61..7cfbff89 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,6 @@ jobs: - uses: actions/checkout@master - name: Enable static CRT linkage run: | - mkdir .cargo echo '[target.x86_64-pc-windows-msvc]' >> .cargo/config echo 'rustflags = ["-Ctarget-feature=+crt-static"]' >> .cargo/config - name: Run the application without starting the shell -- cgit From b27762554627b8e89f2b840b1a8a512e22d4cd87 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 16:26:43 +0200 Subject: Revert "Chore: Apply clippy map transformations" This reverts commit c997aad85d7ee6e77085e50e5e599002549d228f. --- core/src/mouse/click.rs | 4 +++- examples/integration/src/main.rs | 6 ++---- examples/screenshot/src/main.rs | 5 ++++- wgpu/src/image/vector.rs | 2 +- wgpu/src/triangle.rs | 2 +- winit/src/application/state.rs | 3 ++- winit/src/clipboard.rs | 3 ++- 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/core/src/mouse/click.rs b/core/src/mouse/click.rs index 53354098..9cc44a71 100644 --- a/core/src/mouse/click.rs +++ b/core/src/mouse/click.rs @@ -69,6 +69,8 @@ impl Click { }; self.position == new_position - && duration.is_some_and(|duration| duration.as_millis() <= 300) + && duration + .map(|duration| duration.as_millis() <= 300) + .unwrap_or(false) } } diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 4415fefa..c26d52fe 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -200,10 +200,8 @@ pub fn main() -> Result<(), Box> { viewport.scale_factor(), ) }) - .map_or( - mouse::Cursor::Unavailable, - mouse::Cursor::Available, - ), + .map(mouse::Cursor::Available) + .unwrap_or(mouse::Cursor::Unavailable), &mut renderer, &Theme::Dark, &renderer::Style { diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index fa06d3e9..ab0a2ae3 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -298,7 +298,10 @@ fn numeric_input( ) -> Element<'_, Option> { text_input( placeholder, - &value.as_ref().map_or_else(String::new, ToString::to_string), + &value + .as_ref() + .map(ToString::to_string) + .unwrap_or_else(String::new), ) .on_input(move |text| { if text.is_empty() { diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index e8baae4f..6582bb82 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -56,7 +56,7 @@ impl Cache { .ok() }); - tree.map_or(Svg::NotFound, Svg::Loaded) + tree.map(Svg::Loaded).unwrap_or(Svg::NotFound) } svg::Data::Bytes(bytes) => { match usvg::Tree::from_data(bytes, &usvg::Options::default()) { diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index f8014ceb..c55b93bf 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -349,7 +349,7 @@ fn multisample_state( antialiasing: Option, ) -> wgpu::MultisampleState { wgpu::MultisampleState { - count: antialiasing.map_or(1, Antialiasing::sample_count), + count: antialiasing.map(|a| a.sample_count()).unwrap_or(1), mask: !0, alpha_to_coverage_enabled: false, } diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index 9d1d5dcf..e655529a 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -97,7 +97,8 @@ where self.viewport.scale_factor(), ) }) - .map_or(mouse::Cursor::Unavailable, mouse::Cursor::Available) + .map(mouse::Cursor::Available) + .unwrap_or(mouse::Cursor::Unavailable) } /// Returns the current keyboard modifiers of the [`State`]. diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 0a09c255..f7a32868 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -17,7 +17,8 @@ impl Clipboard { pub fn connect(window: &winit::window::Window) -> Clipboard { let state = window_clipboard::Clipboard::connect(window) .ok() - .map_or(State::Unavailable, State::Connected); + .map(State::Connected) + .unwrap_or(State::Unavailable); Clipboard { state } } -- cgit From b8ddd158da1b4e73e67fd090f8d36ed07f191874 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 16:27:54 +0200 Subject: Simplify `map` call in `iced_wgpu::triangle` --- wgpu/src/triangle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index c55b93bf..644c9f84 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -349,7 +349,7 @@ fn multisample_state( antialiasing: Option, ) -> wgpu::MultisampleState { wgpu::MultisampleState { - count: antialiasing.map(|a| a.sample_count()).unwrap_or(1), + count: antialiasing.map(Antialiasing::sample_count).unwrap_or(1), mask: !0, alpha_to_coverage_enabled: false, } -- cgit From f137d71e8fb926e784680d56d1cfa6817c3710a1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 16:40:03 +0200 Subject: Centralize `clippy` lints in `.cargo/config.toml` --- .cargo/config.toml | 9 ++++++++- core/src/lib.rs | 6 ------ futures/src/lib.rs | 6 ------ graphics/src/lib.rs | 6 ------ renderer/src/lib.rs | 12 +----------- runtime/src/lib.rs | 5 ----- src/lib.rs | 6 ------ style/src/lib.rs | 6 ------ tiny_skia/src/lib.rs | 12 +----------- wgpu/src/lib.rs | 6 ------ widget/src/lib.rs | 6 ------ widget/src/text_input/value.rs | 11 ++++++----- winit/src/lib.rs | 6 ------ 13 files changed, 16 insertions(+), 81 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 9e265aa9..e3e32033 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,6 +2,7 @@ lint = """ clippy --workspace --no-deps -- \ -D warnings \ + -A clippy::type_complexity \ -D clippy::semicolon_if_nothing_returned \ -D clippy::trivially-copy-pass-by-ref \ -D clippy::default_trait_access \ @@ -9,9 +10,15 @@ clippy --workspace --no-deps -- \ -D clippy::redundant-closure-for-method-calls \ -D clippy::filter_map_next \ -D clippy::manual_let_else \ - -D clippy::unused_async + -D clippy::unused_async \ + -D clippy::from_over_into \ + -D clippy::needless_borrow \ + -D clippy::new_without_default \ + -D clippy::useless_conversion """ +#![allow(clippy::inherent_to_string, clippy::type_complexity)] + nitpick = """ clippy --workspace --no-deps -- \ -D warnings \ diff --git a/core/src/lib.rs b/core/src/lib.rs index 1bfba7bd..54ea5839 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -14,14 +14,8 @@ missing_debug_implementations, missing_docs, unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion, rustdoc::broken_intra_doc_links )] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] pub mod alignment; pub mod clipboard; pub mod event; diff --git a/futures/src/lib.rs b/futures/src/lib.rs index abc46176..d54ba18a 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -9,14 +9,8 @@ missing_debug_implementations, missing_docs, unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion, rustdoc::broken_intra_doc_links )] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub use futures; pub use iced_core as core; diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 01a358ca..a0729058 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -13,14 +13,8 @@ //missing_docs, unsafe_code, unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion, rustdoc::broken_intra_doc_links )] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] mod antialiasing; mod error; diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 1347ce04..7594d532 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -1,15 +1,5 @@ #![forbid(rust_2018_idioms)] -#![deny( - unsafe_code, - unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion, - rustdoc::broken_intra_doc_links -)] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] +#![deny(unsafe_code, unused_results, rustdoc::broken_intra_doc_links)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod compositor; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index c3261cca..29e94d65 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -13,11 +13,6 @@ missing_debug_implementations, missing_docs, unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion, rustdoc::broken_intra_doc_links )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] diff --git a/src/lib.rs b/src/lib.rs index 3cbe716a..cb6e86d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -156,14 +156,8 @@ missing_debug_implementations, missing_docs, unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion, rustdoc::broken_intra_doc_links )] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] use iced_widget::graphics; use iced_widget::renderer; diff --git a/style/src/lib.rs b/style/src/lib.rs index 0c555ed8..30f17a44 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -10,16 +10,10 @@ #![forbid(unsafe_code, rust_2018_idioms)] #![deny( unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion, missing_docs, unused_results, rustdoc::broken_intra_doc_links )] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] pub use iced_core as core; pub mod application; diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index e48468e9..ec8012be 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -1,15 +1,5 @@ #![forbid(rust_2018_idioms)] -#![deny( - unsafe_code, - unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion, - rustdoc::broken_intra_doc_links -)] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] +#![deny(unsafe_code, unused_results, rustdoc::broken_intra_doc_links)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod window; diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 2f483751..6d26723e 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -26,14 +26,8 @@ //missing_docs, unsafe_code, unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion, rustdoc::broken_intra_doc_links )] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod layer; pub mod primitive; diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 7e204171..6feb948c 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -7,14 +7,8 @@ missing_debug_implementations, missing_docs, unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion, rustdoc::broken_intra_doc_links )] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub use iced_renderer as renderer; pub use iced_renderer::graphics; diff --git a/widget/src/text_input/value.rs b/widget/src/text_input/value.rs index d1b056c8..46a1f754 100644 --- a/widget/src/text_input/value.rs +++ b/widget/src/text_input/value.rs @@ -89,11 +89,6 @@ impl Value { Self { graphemes } } - /// Converts the [`Value`] into a `String`. - pub fn to_string(&self) -> String { - self.graphemes.concat() - } - /// Inserts a new `char` at the given grapheme `index`. pub fn insert(&mut self, index: usize, c: char) { self.graphemes.insert(index, c.to_string()); @@ -131,3 +126,9 @@ impl Value { } } } + +impl std::fmt::Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.graphemes.concat()) + } +} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 9852a182..95b55bb9 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -22,15 +22,9 @@ missing_debug_implementations, missing_docs, unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion, unsafe_code, rustdoc::broken_intra_doc_links )] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub use iced_graphics as graphics; pub use iced_runtime as runtime; -- cgit From bc9bb28b1ccd1248d63ccdfef2f57d7aa837abbb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Sep 2023 16:41:08 +0200 Subject: Fix `cargo nitpick` alias --- .cargo/config.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/.cargo/config.toml b/.cargo/config.toml index e3e32033..3e02dda8 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -23,6 +23,7 @@ nitpick = """ clippy --workspace --no-deps -- \ -D warnings \ -D clippy::pedantic \ + -A clippy::type_complexity \ -A clippy::must_use_candidate \ -A clippy::return_self_not_must_use \ -A clippy::needless_pass_by_value \ -- cgit From 25d47c3238ce23854e2c78e2bd9ad2b1f4b326b3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Sep 2023 06:05:46 +0200 Subject: Remove `rangemap` patch in `Cargo.toml` --- Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8899fa67..77d4c647 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -163,7 +163,3 @@ winit = { git = "https://github.com/iced-rs/winit.git", rev = "c52db2045d0a2f1b8 [patch.crates-io.cosmic-text] git = "https://github.com/hecrj/cosmic-text.git" branch = "respect-fontconfig-aliases" - -[patch.crates-io.rangemap] -git = "https://github.com/hecrj/rangemap.git" -branch = "fix/partial-eq" -- cgit From da5dd2526a2d9ee27e9405ed19c0f7a641160c54 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Sep 2023 06:07:19 +0200 Subject: Round `ScrollDelta::Lines` in `TextEditor` --- widget/src/text_editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 4191e02c..ac927fbc 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -545,7 +545,7 @@ impl Update { action(Action::Scroll { lines: match delta { mouse::ScrollDelta::Lines { y, .. } => { - -y as i32 * 4 + -y.round() as i32 * 4 } mouse::ScrollDelta::Pixels { y, .. } => { -y.signum() as i32 -- cgit From 7373dd856b8837c2d91067b45e43b8f0e767c917 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Sep 2023 06:13:08 +0200 Subject: Scroll at least one line on macOS in `TextEditor` --- widget/src/text_editor.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index ac927fbc..76f3cc18 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -545,7 +545,11 @@ impl Update { action(Action::Scroll { lines: match delta { mouse::ScrollDelta::Lines { y, .. } => { - -y.round() as i32 * 4 + if y > 0.0 { + -(y * 4.0).min(1.0) as i32 + } else { + 0 + } } mouse::ScrollDelta::Pixels { y, .. } => { -y.signum() as i32 -- cgit From 68d49459ce0e8b28e56b71970cb26e66ac1b01b4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Sep 2023 06:17:47 +0200 Subject: Fix vertical scroll for `TextEditor` --- widget/src/text_editor.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 76f3cc18..e8187b9c 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -545,8 +545,9 @@ impl Update { action(Action::Scroll { lines: match delta { mouse::ScrollDelta::Lines { y, .. } => { - if y > 0.0 { - -(y * 4.0).min(1.0) as i32 + if y.abs() > 0.0 { + (y.signum() * -(y.abs() * 4.0).max(1.0)) + as i32 } else { 0 } -- cgit From 70e49df4289b925d24f92ce5c91ef2b03dbc54e3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Sep 2023 05:50:31 +0200 Subject: Fix selection clipping out of bounds in `TextEditor` --- widget/src/text_editor.rs | 57 +++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index e8187b9c..c142c22d 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -406,38 +406,47 @@ where style.text_color, ); + let translation = Vector::new( + bounds.x + self.padding.left, + bounds.y + self.padding.top, + ); + if state.is_focused { match internal.editor.cursor() { Cursor::Caret(position) => { - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: position.x + bounds.x + self.padding.left, - y: position.y + bounds.y + self.padding.top, - width: 1.0, - height: self - .line_height - .to_absolute(self.text_size.unwrap_or_else( - || renderer.default_size(), - )) - .into(), + let position = position + translation; + + if bounds.contains(position) { + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: position.x, + y: position.y, + width: 1.0, + height: self + .line_height + .to_absolute( + self.text_size.unwrap_or_else( + || renderer.default_size(), + ), + ) + .into(), + }, + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, }, - border_radius: 0.0.into(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - theme.value_color(&self.style), - ); + theme.value_color(&self.style), + ); + } } Cursor::Selection(ranges) => { - for range in ranges { + for range in ranges.into_iter().filter_map(|range| { + bounds.intersection(&(range + translation)) + }) { renderer.fill_quad( renderer::Quad { - bounds: range - + Vector::new( - bounds.x + self.padding.left, - bounds.y + self.padding.top, - ), + bounds: range, border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, -- cgit From af21cf82492bf7ffa1241cebae182c5916fc07d1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Sep 2023 05:55:27 +0200 Subject: Remove `patch.crates-io` section for `cosmic-text` in `Cargo.toml` --- Cargo.toml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 77d4c647..888e2df8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,10 +121,10 @@ iced_winit = { version = "0.12", path = "winit" } async-std = "1.0" bitflags = "1.0" bytemuck = { version = "1.0", features = ["derive"] } -cosmic-text = "0.9" +cosmic-text = { git = "https://github.com/pop-os/cosmic-text.git", rev = "30398c2f0cb79267d440870bc47967579e31a2ae" } futures = "0.3" glam = "0.24" -glyphon = { git = "https://github.com/grovesNL/glyphon.git", rev = "20f0f8fa80e0d0df4c63634ce9176fa489546ca9" } +glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "0a8366be5ec6d48c3e10c996ba840936992d878f" } guillotiere = "0.6" half = "2.2" image = "0.24" @@ -159,7 +159,3 @@ wgpu = "0.17" winapi = "0.3" window_clipboard = "0.3" winit = { git = "https://github.com/iced-rs/winit.git", rev = "c52db2045d0a2f1b8d9923870de1d4ab1994146e", default-features = false } - -[patch.crates-io.cosmic-text] -git = "https://github.com/hecrj/cosmic-text.git" -branch = "respect-fontconfig-aliases" -- cgit From 8cc19de254c37d3123d5ea1b6513f1f34d35c7c8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Sep 2023 06:00:51 +0200 Subject: Add `text` helper method for `text_editor::Content` --- examples/editor/src/main.rs | 19 +------------------ widget/src/text_editor.rs | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index f49ca6e8..a69e1f54 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -114,25 +114,8 @@ impl Application for Editor { } else { self.is_loading = true; - let mut contents = self.content.lines().enumerate().fold( - String::new(), - |mut contents, (i, line)| { - if i > 0 { - contents.push('\n'); - } - - contents.push_str(&line); - - contents - }, - ); - - if !contents.ends_with('\n') { - contents.push('\n'); - } - Command::perform( - save_file(self.file.clone(), contents), + save_file(self.file.clone(), self.content.text()), Message::FileSaved, ) } diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index c142c22d..6d25967e 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -190,6 +190,27 @@ where } } + pub fn text(&self) -> String { + let mut text = self.lines().enumerate().fold( + String::new(), + |mut contents, (i, line)| { + if i > 0 { + contents.push('\n'); + } + + contents.push_str(&line); + + contents + }, + ); + + if !text.ends_with('\n') { + text.push('\n'); + } + + text + } + pub fn selection(&self) -> Option { self.0.borrow().editor.selection() } -- cgit From e0233ebc3ce4791d094c52eeef81cce78b9bc578 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 3 Aug 2023 10:19:28 -0700 Subject: Fix `Command::perform` to return a `Command` This seems like clearly the correct thing to do here. If the type bound on `Command` isn't specified, it makes no difference, since the generic is inferred in a way that works with either definition. But this is important if `Command` is aliased with a concrete type. --- runtime/src/command.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/src/command.rs b/runtime/src/command.rs index cd4c51ff..b74097bd 100644 --- a/runtime/src/command.rs +++ b/runtime/src/command.rs @@ -40,9 +40,9 @@ impl Command { /// Creates a [`Command`] that performs the action of the given future. pub fn perform( - future: impl Future + 'static + MaybeSend, - f: impl FnOnce(T) -> A + 'static + MaybeSend, - ) -> Command { + future: impl Future + 'static + MaybeSend, + f: impl FnOnce(A) -> T + 'static + MaybeSend, + ) -> Command { use iced_futures::futures::FutureExt; Command::single(Action::Future(Box::pin(future.map(f)))) -- cgit From 54e6d2b5fa1fe29e2e3588b51f6cfff36563cefc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 18 Oct 2023 17:49:19 -0500 Subject: Fix lint in `screenshot` example --- examples/screenshot/src/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index ab0a2ae3..f781a401 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -298,10 +298,7 @@ fn numeric_input( ) -> Element<'_, Option> { text_input( placeholder, - &value - .as_ref() - .map(ToString::to_string) - .unwrap_or_else(String::new), + &value.as_ref().map(ToString::to_string).unwrap_or_default(), ) .on_input(move |text| { if text.is_empty() { -- cgit From f1b1344d59fa7354615f560bd25ed01ad0c9f865 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 22 Oct 2023 15:08:08 +0200 Subject: Run `cargo update` before `cargo audit` in `audit` workflow --- .github/workflows/audit.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index e9f4b0c5..bfb617fb 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -12,6 +12,8 @@ jobs: - name: Install cargo-audit run: cargo install cargo-audit - uses: actions/checkout@master + - name: Resolve dependencies + run: cargo update - name: Audit vulnerabilities run: cargo audit -- cgit From 86b877517feb15b2155c6cfef29246a3f281c8ae Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 27 Oct 2023 03:21:40 +0200 Subject: Update `wgpu` to `0.18` and `cosmic-text` to `0.10` --- Cargo.toml | 6 +++--- examples/integration/src/scene.rs | 4 +++- wgpu/src/backend.rs | 8 ++++++-- wgpu/src/color.rs | 4 +++- wgpu/src/triangle.rs | 7 ++++++- wgpu/src/triangle/msaa.rs | 4 +++- 6 files changed, 24 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index af74a3cf..bb8b4752 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,10 +114,10 @@ iced_winit = { version = "0.12", path = "winit" } async-std = "1.0" bitflags = "1.0" bytemuck = { version = "1.0", features = ["derive"] } -cosmic-text = "0.9" +cosmic-text = "0.10" futures = "0.3" glam = "0.24" -glyphon = { git = "https://github.com/grovesNL/glyphon.git", rev = "20f0f8fa80e0d0df4c63634ce9176fa489546ca9" } +glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "2caa9fc5e5923c1d827d177c3619cab7e9885b85" } guillotiere = "0.6" half = "2.2" image = "0.24" @@ -147,7 +147,7 @@ unicode-segmentation = "1.0" wasm-bindgen-futures = "0.4" wasm-timer = "0.2" web-sys = "0.3" -wgpu = "0.17" +wgpu = "0.18" winapi = "0.3" window_clipboard = "0.3" winit = { git = "https://github.com/iced-rs/winit.git", rev = "c52db2045d0a2f1b8d9923870de1d4ab1994146e", default-features = false } diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs index 01808f40..e29558bf 100644 --- a/examples/integration/src/scene.rs +++ b/examples/integration/src/scene.rs @@ -36,10 +36,12 @@ impl Scene { a: a as f64, } }), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, }) } diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 65c63f19..32b8a189 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -222,10 +222,12 @@ impl Backend { }), None => wgpu::LoadOp::Load, }, - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, }, )); @@ -271,11 +273,13 @@ impl Backend { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, }, )], depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, }, )); } diff --git a/wgpu/src/color.rs b/wgpu/src/color.rs index 20827e3c..4598b0a6 100644 --- a/wgpu/src/color.rs +++ b/wgpu/src/color.rs @@ -143,10 +143,12 @@ pub fn convert( resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, }); pass.set_pipeline(&pipeline); diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 644c9f84..69270a73 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -300,10 +300,15 @@ impl Pipeline { wgpu::RenderPassColorAttachment { view: attachment, resolve_target, - ops: wgpu::Operations { load, store: true }, + ops: wgpu::Operations { + load, + store: wgpu::StoreOp::Store, + }, }, )], depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, }); let layer = &mut self.layers[layer]; diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index 320b5b12..14abd20b 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -167,10 +167,12 @@ impl Blit { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, }); render_pass.set_pipeline(&self.pipeline); -- cgit From a00ebcde3d698bc6b59a7a258e91c3612a6faaaf Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 27 Oct 2023 03:24:59 +0200 Subject: Remove unnecessary `into_iter` call in `iced_graphics` --- graphics/src/text.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/graphics/src/text.rs b/graphics/src/text.rs index bc06aa3c..a7d52645 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -22,12 +22,11 @@ pub struct FontSystem { impl FontSystem { pub fn new() -> Self { FontSystem { - raw: RwLock::new(cosmic_text::FontSystem::new_with_fonts( - [cosmic_text::fontdb::Source::Binary(Arc::new( + raw: RwLock::new(cosmic_text::FontSystem::new_with_fonts([ + cosmic_text::fontdb::Source::Binary(Arc::new( include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), - ))] - .into_iter(), - )), + )), + ])), version: Version::default(), } } -- cgit From 625cd745f38215b1cb8f629cdc6d2fa41c9a739a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 27 Oct 2023 05:04:14 +0200 Subject: Write documentation for the new text APIs --- core/src/lib.rs | 2 +- core/src/mouse/click.rs | 1 + core/src/text.rs | 2 ++ core/src/text/editor.rs | 51 +++++++++++++++++++++++++++++++++++++++++- core/src/text/highlighter.rs | 30 ++++++++++++++++++++----- examples/editor/src/main.rs | 10 ++++----- graphics/src/lib.rs | 2 +- graphics/src/text.rs | 13 +++++++++++ graphics/src/text/cache.rs | 19 ++++++++++++++++ graphics/src/text/editor.rs | 12 ++++++++++ graphics/src/text/paragraph.rs | 14 ++++++++++++ style/src/lib.rs | 2 +- wgpu/src/layer/text.rs | 7 +++++- wgpu/src/lib.rs | 2 +- widget/src/lib.rs | 4 ++-- widget/src/text_editor.rs | 36 +++++++++++++++++++++++++---- 16 files changed, 185 insertions(+), 22 deletions(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index 9eb3da34..54ea5839 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -12,7 +12,7 @@ #![forbid(unsafe_code, rust_2018_idioms)] #![deny( missing_debug_implementations, - // missing_docs, + missing_docs, unused_results, rustdoc::broken_intra_doc_links )] diff --git a/core/src/mouse/click.rs b/core/src/mouse/click.rs index b427da6c..6f3844be 100644 --- a/core/src/mouse/click.rs +++ b/core/src/mouse/click.rs @@ -61,6 +61,7 @@ impl Click { self.kind } + /// Returns the position of the [`Click`]. pub fn position(&self) -> Point { self.position } diff --git a/core/src/text.rs b/core/src/text.rs index 9b9c753c..546d0b5c 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -204,6 +204,8 @@ pub trait Renderer: crate::Renderer { color: Color, ); + /// Draws the given [`Editor`] at the given position and with the given + /// [`Color`]. fn fill_editor( &mut self, editor: &Self::Editor, diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs index e9d66ce9..ebb0eee2 100644 --- a/core/src/text/editor.rs +++ b/core/src/text/editor.rs @@ -1,25 +1,36 @@ +//! Edit text. use crate::text::highlighter::{self, Highlighter}; use crate::text::LineHeight; use crate::{Pixels, Point, Rectangle, Size}; use std::sync::Arc; +/// A component that can be used by widgets to edit multi-line text. pub trait Editor: Sized + Default { + /// The [`Font`] of the [`Editor`]. type Font: Copy + PartialEq + Default; /// Creates a new [`Editor`] laid out with the given text. fn with_text(text: &str) -> Self; + /// Returns the current [`Cursor`] of the [`Editor`]. fn cursor(&self) -> Cursor; + /// Returns the current cursor position of the [`Editor`]. + /// + /// Line and column, respectively. fn cursor_position(&self) -> (usize, usize); + /// Returns the current selected text of the [`Editor`]. fn selection(&self) -> Option; + /// Returns the text of the given line in the [`Editor`], if it exists. fn line(&self, index: usize) -> Option<&str>; + /// Returns the amount of lines in the [`Editor`]. fn line_count(&self) -> usize; + /// Performs an [`Action`] on the [`Editor`]. fn perform(&mut self, action: Action); /// Returns the current boundaries of the [`Editor`]. @@ -35,6 +46,7 @@ pub trait Editor: Sized + Default { new_highlighter: &mut impl Highlighter, ); + /// Runs a text [`Highlighter`] in the [`Editor`]. fn highlight( &mut self, font: Self::Font, @@ -43,50 +55,83 @@ pub trait Editor: Sized + Default { ); } +/// An interaction with an [`Editor`]. #[derive(Debug, Clone, PartialEq)] pub enum Action { + /// Apply a [`Motion`]. Move(Motion), + /// Select text with a given [`Motion`]. Select(Motion), + /// Select the word at the current cursor. SelectWord, + /// Select the line at the current cursor. SelectLine, + /// Perform an [`Edit`]. Edit(Edit), + /// Click the [`Editor`] at the given [`Point`]. Click(Point), + /// Drag the mouse on the [`Editor`] to the given [`Point`]. Drag(Point), - Scroll { lines: i32 }, + /// Scroll the [`Editor`] a certain amount of lines. + Scroll { + /// The amount of lines to scroll. + lines: i32, + }, } impl Action { + /// Returns whether the [`Action`] is an editing action. pub fn is_edit(&self) -> bool { matches!(self, Self::Edit(_)) } } +/// An action that edits text. #[derive(Debug, Clone, PartialEq)] pub enum Edit { + /// Insert the given character. Insert(char), + /// Paste the given text. Paste(Arc), + /// Break the current line. Enter, + /// Delete the previous character. Backspace, + /// Delete the next character. Delete, } +/// A cursor movement. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Motion { + /// Move left. Left, + /// Move right. Right, + /// Move up. Up, + /// Move down. Down, + /// Move to the left boundary of a word. WordLeft, + /// Move to the right boundary of a word. WordRight, + /// Move to the start of the line. Home, + /// Move to the end of the line. End, + /// Move to the start of the previous window. PageUp, + /// Move to the start of the next window. PageDown, + /// Move to the start of the text. DocumentStart, + /// Move to the end of the text. DocumentEnd, } impl Motion { + /// Widens the [`Motion`], if possible. pub fn widen(self) -> Self { match self { Self::Left => Self::WordLeft, @@ -97,6 +142,7 @@ impl Motion { } } + /// Returns the [`Direction`] of the [`Motion`]. pub fn direction(&self) -> Direction { match self { Self::Left @@ -115,9 +161,12 @@ impl Motion { } } +/// A direction in some text. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Direction { + /// <- Left, + /// -> Right, } diff --git a/core/src/text/highlighter.rs b/core/src/text/highlighter.rs index 9a9cff89..a0535228 100644 --- a/core/src/text/highlighter.rs +++ b/core/src/text/highlighter.rs @@ -1,31 +1,48 @@ +//! Highlight text. use crate::Color; use std::ops::Range; +/// A type capable of highlighting text. +/// +/// A [`Highlighter`] highlights lines in sequence. When a line changes, +/// it must be notified and the lines after the changed one must be fed +/// again to the [`Highlighter`]. pub trait Highlighter: 'static { + /// The settings to configure the [`Highlighter`]. type Settings: PartialEq + Clone; + + /// The output of the [`Highlighter`]. type Highlight; + /// The highlight iterator type. type Iterator<'a>: Iterator, Self::Highlight)> where Self: 'a; + /// Creates a new [`Highlighter`] from its [`Self::Settings`]. fn new(settings: &Self::Settings) -> Self; + /// Updates the [`Highlighter`] with some new [`Self::Settings`]. fn update(&mut self, new_settings: &Self::Settings); + /// Notifies the [`Highlighter`] that the line at the given index has changed. fn change_line(&mut self, line: usize); + /// Highlights the given line. + /// + /// If a line changed prior to this, the first line provided here will be the + /// line that changed. fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_>; + /// Returns the current line of the [`Highlighter`]. + /// + /// If `change_line` has been called, this will normally be the least index + /// that changed. fn current_line(&self) -> usize; } -#[derive(Debug, Clone, Copy)] -pub struct Style { - pub color: Color, -} - +/// A highlighter that highlights nothing. #[derive(Debug, Clone, Copy)] pub struct PlainText; @@ -52,9 +69,12 @@ impl Highlighter for PlainText { } } +/// The format of some text. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Format { + /// The [`Color`] of the text. pub color: Option, + /// The `Font` of the text. pub font: Option, } diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index a69e1f54..03d1e283 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -34,7 +34,7 @@ struct Editor { #[derive(Debug, Clone)] enum Message { - Edit(text_editor::Action), + ActionPerformed(text_editor::Action), ThemeSelected(highlighter::Theme), NewFile, OpenFile, @@ -68,10 +68,10 @@ impl Application for Editor { fn update(&mut self, message: Message) -> Command { match message { - Message::Edit(action) => { + Message::ActionPerformed(action) => { self.is_dirty = self.is_dirty || action.is_edit(); - self.content.edit(action); + self.content.perform(action); Command::none() } @@ -103,7 +103,7 @@ impl Application for Editor { if let Ok((path, contents)) = result { self.file = Some(path); - self.content = text_editor::Content::with(&contents); + self.content = text_editor::Content::with_text(&contents); } Command::none() @@ -191,7 +191,7 @@ impl Application for Editor { column![ controls, text_editor(&self.content) - .on_edit(Message::Edit) + .on_action(Message::ActionPerformed) .highlight::( highlighter::Settings { theme: self.theme, diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index a0729058..7a213909 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -10,7 +10,7 @@ #![forbid(rust_2018_idioms)] #![deny( missing_debug_implementations, - //missing_docs, + missing_docs, unsafe_code, unused_results, rustdoc::broken_intra_doc_links diff --git a/graphics/src/text.rs b/graphics/src/text.rs index c10eacad..7261900e 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -1,3 +1,4 @@ +//! Draw text. pub mod cache; pub mod editor; pub mod paragraph; @@ -17,6 +18,7 @@ use once_cell::sync::OnceCell; use std::borrow::Cow; use std::sync::{Arc, RwLock}; +/// Returns the global [`FontSystem`]. pub fn font_system() -> &'static RwLock { static FONT_SYSTEM: OnceCell> = OnceCell::new(); @@ -32,6 +34,7 @@ pub fn font_system() -> &'static RwLock { }) } +/// A set of system fonts. #[allow(missing_debug_implementations)] pub struct FontSystem { raw: cosmic_text::FontSystem, @@ -39,10 +42,12 @@ pub struct FontSystem { } impl FontSystem { + /// Returns the raw [`cosmic_text::FontSystem`]. pub fn raw(&mut self) -> &mut cosmic_text::FontSystem { &mut self.raw } + /// Loads a font from its bytes. pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { let _ = self.raw.db_mut().load_font_source( cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())), @@ -51,14 +56,19 @@ impl FontSystem { self.version = Version(self.version.0 + 1); } + /// Returns the current [`Version`] of the [`FontSystem`]. + /// + /// Loading a font will increase the version of a [`FontSystem`]. pub fn version(&self) -> Version { self.version } } +/// A version number. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct Version(u32); +/// Measures the dimensions of the given [`cosmic_text::Buffer`]. pub fn measure(buffer: &cosmic_text::Buffer) -> Size { let (width, total_lines) = buffer .layout_runs() @@ -69,6 +79,7 @@ pub fn measure(buffer: &cosmic_text::Buffer) -> Size { Size::new(width, total_lines as f32 * buffer.metrics().line_height) } +/// Returns the attributes of the given [`Font`]. pub fn to_attributes(font: Font) -> cosmic_text::Attrs<'static> { cosmic_text::Attrs::new() .family(to_family(font.family)) @@ -124,6 +135,7 @@ fn to_style(style: font::Style) -> cosmic_text::Style { } } +/// Converts some [`Shaping`] strategy to a [`cosmic_text::Shaping`] strategy. pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping { match shaping { Shaping::Basic => cosmic_text::Shaping::Basic, @@ -131,6 +143,7 @@ pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping { } } +/// Converts some [`Color`] to a [`cosmic_text::Color`]. pub fn to_color(color: Color) -> cosmic_text::Color { let [r, g, b, a] = color::pack(color).components(); diff --git a/graphics/src/text/cache.rs b/graphics/src/text/cache.rs index 577c4687..b3293dd4 100644 --- a/graphics/src/text/cache.rs +++ b/graphics/src/text/cache.rs @@ -1,3 +1,4 @@ +//! Cache text. use crate::core::{Font, Size}; use crate::text; @@ -5,6 +6,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use std::collections::hash_map; use std::hash::{BuildHasher, Hash, Hasher}; +/// A store of recently used sections of text. #[allow(missing_debug_implementations)] #[derive(Default)] pub struct Cache { @@ -21,14 +23,17 @@ type HashBuilder = twox_hash::RandomXxHashBuilder64; type HashBuilder = std::hash::BuildHasherDefault; impl Cache { + /// Creates a new empty [`Cache`]. pub fn new() -> Self { Self::default() } + /// Gets the text [`Entry`] with the given [`KeyHash`]. pub fn get(&self, key: &KeyHash) -> Option<&Entry> { self.entries.get(key) } + /// Allocates a text [`Entry`] if it is not already present in the [`Cache`]. pub fn allocate( &mut self, font_system: &mut cosmic_text::FontSystem, @@ -88,6 +93,9 @@ impl Cache { (hash, self.entries.get_mut(&hash).unwrap()) } + /// Trims the [`Cache`]. + /// + /// This will clear the sections of text that have not been used since the last `trim`. pub fn trim(&mut self) { self.entries .retain(|key, _| self.recently_used.contains(key)); @@ -99,13 +107,20 @@ impl Cache { } } +/// A cache key representing a section of text. #[derive(Debug, Clone, Copy)] pub struct Key<'a> { + /// The content of the text. pub content: &'a str, + /// The size of the text. pub size: f32, + /// The line height of the text. pub line_height: f32, + /// The [`Font`] of the text. pub font: Font, + /// The bounds of the text. pub bounds: Size, + /// The shaping strategy of the text. pub shaping: text::Shaping, } @@ -123,10 +138,14 @@ impl Key<'_> { } } +/// The hash of a [`Key`]. pub type KeyHash = u64; +/// A cache entry. #[allow(missing_debug_implementations)] pub struct Entry { + /// The buffer of text, ready for drawing. pub buffer: cosmic_text::Buffer, + /// The minimum bounds of the text. pub min_bounds: Size, } diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index a05312dc..d5262ae8 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -1,3 +1,4 @@ +//! Draw and edit text. use crate::core::text::editor::{ self, Action, Cursor, Direction, Edit, Motion, }; @@ -11,6 +12,7 @@ use cosmic_text::Edit as _; use std::fmt; use std::sync::{self, Arc}; +/// A multi-line text editor. #[derive(Debug, PartialEq)] pub struct Editor(Option>); @@ -23,14 +25,21 @@ struct Internal { } impl Editor { + /// Creates a new empty [`Editor`]. pub fn new() -> Self { Self::default() } + /// Returns the buffer of the [`Editor`]. pub fn buffer(&self) -> &cosmic_text::Buffer { self.internal().editor.buffer() } + /// Creates a [`Weak`] reference to the [`Editor`]. + /// + /// This is useful to avoid cloning the [`Editor`] when + /// referential guarantees are unnecessary. For instance, + /// when creating a rendering tree. pub fn downgrade(&self) -> Weak { let editor = self.internal(); @@ -662,13 +671,16 @@ impl fmt::Debug for Internal { } } +/// A weak reference to an [`Editor`]. #[derive(Debug, Clone)] pub struct Weak { raw: sync::Weak, + /// The bounds of the [`Editor`]. pub bounds: Size, } impl Weak { + /// Tries to update the reference into an [`Editor`]. pub fn upgrade(&self) -> Option { self.raw.upgrade().map(Some).map(Editor) } diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index d0396e8e..ccfe4a61 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -1,3 +1,4 @@ +//! Draw paragraphs. use crate::core; use crate::core::alignment; use crate::core::text::{Hit, LineHeight, Shaping, Text}; @@ -7,6 +8,7 @@ use crate::text; use std::fmt; use std::sync::{self, Arc}; +/// A bunch of text. #[derive(Clone, PartialEq)] pub struct Paragraph(Option>); @@ -23,14 +25,21 @@ struct Internal { } impl Paragraph { + /// Creates a new empty [`Paragraph`]. pub fn new() -> Self { Self::default() } + /// Returns the buffer of the [`Paragraph`]. pub fn buffer(&self) -> &cosmic_text::Buffer { &self.internal().buffer } + /// Creates a [`Weak`] reference to the [`Paragraph`]. + /// + /// This is useful to avoid cloning the [`Editor`] when + /// referential guarantees are unnecessary. For instance, + /// when creating a rendering tree. pub fn downgrade(&self) -> Weak { let paragraph = self.internal(); @@ -269,15 +278,20 @@ impl Default for Internal { } } +/// A weak reference to a [`Paragraph`]. #[derive(Debug, Clone)] pub struct Weak { raw: sync::Weak, + /// The minimum bounds of the [`Paragraph`]. pub min_bounds: Size, + /// The horizontal alignment of the [`Paragraph`]. pub horizontal_alignment: alignment::Horizontal, + /// The vertical alignment of the [`Paragraph`]. pub vertical_alignment: alignment::Vertical, } impl Weak { + /// Tries to update the reference into a [`Paragraph`]. pub fn upgrade(&self) -> Option { self.raw.upgrade().map(Some).map(Paragraph) } diff --git a/style/src/lib.rs b/style/src/lib.rs index 35460f4b..e4097434 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -10,7 +10,7 @@ #![forbid(unsafe_code, rust_2018_idioms)] #![deny( unused_results, - // missing_docs, + missing_docs, unused_results, rustdoc::broken_intra_doc_links )] diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs index d46b39da..66417cec 100644 --- a/wgpu/src/layer/text.rs +++ b/wgpu/src/layer/text.rs @@ -4,19 +4,24 @@ use crate::core::{Color, Font, Pixels, Point, Rectangle}; use crate::graphics::text::editor; use crate::graphics::text::paragraph; -/// A paragraph of text. +/// A text primitive. #[derive(Debug, Clone)] pub enum Text<'a> { + /// A paragraph. + #[allow(missing_docs)] Paragraph { paragraph: paragraph::Weak, position: Point, color: Color, }, + /// An editor. + #[allow(missing_docs)] Editor { editor: editor::Weak, position: Point, color: Color, }, + /// A cached text. Cached(Cached<'a>), } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 6d26723e..424dfeb3 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -23,7 +23,7 @@ #![forbid(rust_2018_idioms)] #![deny( missing_debug_implementations, - //missing_docs, + missing_docs, unsafe_code, unused_results, rustdoc::broken_intra_doc_links diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 97e4ac58..e3335a98 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -4,8 +4,8 @@ )] #![forbid(unsafe_code, rust_2018_idioms)] #![deny( - // missing_debug_implementations, - // missing_docs, + //missing_debug_implementations, + missing_docs, unused_results, rustdoc::broken_intra_doc_links )] diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 6d25967e..da1905dc 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -1,3 +1,4 @@ +//! Display a multi-line text input for text editing. use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::layout::{self, Layout}; @@ -19,6 +20,7 @@ use std::sync::Arc; pub use crate::style::text_editor::{Appearance, StyleSheet}; pub use text::editor::{Action, Edit, Motion}; +/// A multi-line text input. pub struct TextEditor<'a, Highlighter, Message, Renderer = crate::Renderer> where Highlighter: text::Highlighter, @@ -47,6 +49,7 @@ where Renderer: text::Renderer, Renderer::Theme: StyleSheet, { + /// Creates new [`TextEditor`] with the given [`Content`]. pub fn new(content: &'a Content) -> Self { Self { content, @@ -73,21 +76,34 @@ where Renderer: text::Renderer, Renderer::Theme: StyleSheet, { - pub fn on_edit(mut self, on_edit: impl Fn(Action) -> Message + 'a) -> Self { + /// Sets the message that should be produced when some action is performed in + /// the [`TextEditor`]. + /// + /// If this method is not called, the [`TextEditor`] will be disabled. + pub fn on_action( + mut self, + on_edit: impl Fn(Action) -> Message + 'a, + ) -> Self { self.on_edit = Some(Box::new(on_edit)); self } + /// Sets the [`Font`] of the [`TextEditor`]. + /// + /// [`Font`]: text::Renderer::Font pub fn font(mut self, font: impl Into) -> Self { self.font = Some(font.into()); self } + /// Sets the [`Padding`] of the [`TextEditor`]. pub fn padding(mut self, padding: impl Into) -> Self { self.padding = padding.into(); self } + /// Highlights the [`TextEditor`] with the given [`Highlighter`] and + /// a strategy to turn its highlights into some text format. pub fn highlight( self, settings: H::Settings, @@ -112,6 +128,7 @@ where } } +/// The content of a [`TextEditor`]. pub struct Content(RefCell>) where R: text::Renderer; @@ -128,28 +145,33 @@ impl Content where R: text::Renderer, { + /// Creates an empty [`Content`]. pub fn new() -> Self { - Self::with("") + Self::with_text("") } - pub fn with(text: &str) -> Self { + /// Creates a [`Content`] with the given text. + pub fn with_text(text: &str) -> Self { Self(RefCell::new(Internal { editor: R::Editor::with_text(text), is_dirty: true, })) } - pub fn edit(&mut self, action: Action) { + /// Performs an [`Action`] on the [`Content`]. + pub fn perform(&mut self, action: Action) { let internal = self.0.get_mut(); internal.editor.perform(action); internal.is_dirty = true; } + /// Returns the amount of lines of the [`Content`]. pub fn line_count(&self) -> usize { self.0.borrow().editor.line_count() } + /// Returns the text of the line at the given index, if it exists. pub fn line( &self, index: usize, @@ -160,6 +182,7 @@ where .ok() } + /// Returns an iterator of the text of the lines in the [`Content`]. pub fn lines( &self, ) -> impl Iterator + '_> { @@ -190,6 +213,9 @@ where } } + /// Returns the text of the [`Content`]. + /// + /// Lines are joined with `'\n'`. pub fn text(&self) -> String { let mut text = self.lines().enumerate().fold( String::new(), @@ -211,10 +237,12 @@ where text } + /// Returns the selected text of the [`Content`]. pub fn selection(&self) -> Option { self.0.borrow().editor.selection() } + /// Returns the current cursor position of the [`Content`]. pub fn cursor_position(&self) -> (usize, usize) { self.0.borrow().editor.cursor_position() } -- cgit From e579d8553088c7d17784e7ff8f6e21360c2bd9ef Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 27 Oct 2023 05:08:06 +0200 Subject: Implement missing debug implementations in `iced_widget` --- widget/src/lib.rs | 2 +- widget/src/text_editor.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/widget/src/lib.rs b/widget/src/lib.rs index e3335a98..2f959370 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -4,7 +4,7 @@ )] #![forbid(unsafe_code, rust_2018_idioms)] #![deny( - //missing_debug_implementations, + missing_debug_implementations, missing_docs, unused_results, rustdoc::broken_intra_doc_links diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index da1905dc..ac24920f 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -14,6 +14,7 @@ use crate::core::{ }; use std::cell::RefCell; +use std::fmt; use std::ops::DerefMut; use std::sync::Arc; @@ -21,6 +22,7 @@ pub use crate::style::text_editor::{Appearance, StyleSheet}; pub use text::editor::{Action, Edit, Motion}; /// A multi-line text input. +#[allow(missing_debug_implementations)] pub struct TextEditor<'a, Highlighter, Message, Renderer = crate::Renderer> where Highlighter: text::Highlighter, @@ -257,6 +259,21 @@ where } } +impl fmt::Debug for Content +where + Renderer: text::Renderer, + Renderer::Editor: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let internal = self.0.borrow(); + + f.debug_struct("Content") + .field("editor", &internal.editor) + .field("is_dirty", &internal.is_dirty) + .finish() + } +} + struct State { is_focused: bool, last_click: Option, -- cgit From 57f9024e89256ad3f99a3ab19bdc8524c1defa54 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 27 Oct 2023 05:19:35 +0200 Subject: Fix intra-doc broken links --- .cargo/config.toml | 2 -- core/src/text/editor.rs | 2 +- graphics/src/text/paragraph.rs | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 3e02dda8..85a46cda 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -17,8 +17,6 @@ clippy --workspace --no-deps -- \ -D clippy::useless_conversion """ -#![allow(clippy::inherent_to_string, clippy::type_complexity)] - nitpick = """ clippy --workspace --no-deps -- \ -D warnings \ diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs index ebb0eee2..f3c6e342 100644 --- a/core/src/text/editor.rs +++ b/core/src/text/editor.rs @@ -7,7 +7,7 @@ use std::sync::Arc; /// A component that can be used by widgets to edit multi-line text. pub trait Editor: Sized + Default { - /// The [`Font`] of the [`Editor`]. + /// The font of the [`Editor`]. type Font: Copy + PartialEq + Default; /// Creates a new [`Editor`] laid out with the given text. diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index ccfe4a61..4a08a8f4 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -37,7 +37,7 @@ impl Paragraph { /// Creates a [`Weak`] reference to the [`Paragraph`]. /// - /// This is useful to avoid cloning the [`Editor`] when + /// This is useful to avoid cloning the [`Paragraph`] when /// referential guarantees are unnecessary. For instance, /// when creating a rendering tree. pub fn downgrade(&self) -> Weak { -- cgit From 3ec5ad42251d4f35861f3bed621223e383742b12 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 27 Oct 2023 06:00:28 +0200 Subject: Use upstream repository for `glyphon` dependency --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bb8b4752..03df14c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,7 +117,7 @@ bytemuck = { version = "1.0", features = ["derive"] } cosmic-text = "0.10" futures = "0.3" glam = "0.24" -glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "2caa9fc5e5923c1d827d177c3619cab7e9885b85" } +glyphon = { git = "https://github.com/grovesNL/glyphon.git", rev = "2caa9fc5e5923c1d827d177c3619cab7e9885b85" } guillotiere = "0.6" half = "2.2" image = "0.24" -- cgit From c07315b84eb59daeb9bbe7480f30dc0937ceca13 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 27 Oct 2023 05:53:29 +0200 Subject: Disable maximize window button if `Settings::resizable` is `false` --- winit/src/settings.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 867dad0f..16c9fcdc 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -130,6 +130,12 @@ impl Window { .with_title(title) .with_inner_size(winit::dpi::LogicalSize { width, height }) .with_resizable(self.resizable) + .with_enabled_buttons(if self.resizable { + winit::window::WindowButtons::all() + } else { + winit::window::WindowButtons::CLOSE + | winit::window::WindowButtons::MINIMIZE + }) .with_decorations(self.decorations) .with_transparent(self.transparent) .with_window_icon(self.icon.and_then(conversion::icon)) -- cgit From c8eca4e6bfae82013e6bb08e9d8bf66560b36564 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 27 Oct 2023 16:37:58 +0200 Subject: Improve `TextEditor` scroll interaction with a touchpad --- widget/src/text_editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index ac24920f..1708a2e5 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -628,7 +628,7 @@ impl Update { } } mouse::ScrollDelta::Pixels { y, .. } => { - -y.signum() as i32 + (-y / 4.0) as i32 } }, }) -- cgit From 98e088e731e6fbd5b5035033ae61bda823ced988 Mon Sep 17 00:00:00 2001 From: dtzxporter Date: Tue, 12 Sep 2023 18:15:00 -0400 Subject: Migrate twox-hash -> xxhash_rust. Switch to Xxh3 for better performance. xxhash-rust is more maintained, built against `::core`, so no workaround for wasm is necessary. Switch to Xxh3 for better performance, which shows when loading/hashing image buffers. --- Cargo.toml | 2 +- core/Cargo.toml | 2 +- core/src/hasher.rs | 5 +++-- graphics/Cargo.toml | 6 +----- graphics/src/text/cache.rs | 6 +----- tiny_skia/Cargo.toml | 6 +----- 6 files changed, 8 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1a286b9b..f625f1ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,7 +150,7 @@ thiserror = "1.0" tiny-skia = "0.10" tokio = "1.0" tracing = "0.1" -twox-hash = { version = "1.0", default-features = false } +xxhash-rust = { version = "0.8.7", default-features = false, features = ["xxh3"] } unicode-segmentation = "1.0" wasm-bindgen-futures = "0.4" wasm-timer = "0.2" diff --git a/core/Cargo.toml b/core/Cargo.toml index 7acb7511..82946847 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -14,7 +14,7 @@ keywords.workspace = true bitflags.workspace = true log.workspace = true thiserror.workspace = true -twox-hash.workspace = true +xxhash-rust.workspace = true num-traits.workspace = true palette.workspace = true diff --git a/core/src/hasher.rs b/core/src/hasher.rs index 9d8f75b3..a13d78af 100644 --- a/core/src/hasher.rs +++ b/core/src/hasher.rs @@ -1,6 +1,7 @@ /// The hasher used to compare layouts. -#[derive(Debug, Default)] -pub struct Hasher(twox_hash::XxHash64); +#[allow(missing_debug_implementations)] // Doesn't really make sense to have debug on the hasher state anyways. +#[derive(Default)] +pub struct Hasher(xxhash_rust::xxh3::Xxh3); impl core::hash::Hasher for Hasher { fn write(&mut self, bytes: &[u8]) { diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 3165810b..a7aea352 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -33,8 +33,8 @@ once_cell.workspace = true raw-window-handle.workspace = true rustc-hash.workspace = true thiserror.workspace = true -twox-hash.workspace = true unicode-segmentation.workspace = true +xxhash-rust.workspace = true image.workspace = true image.optional = true @@ -44,7 +44,3 @@ kamadak-exif.optional = true lyon_path.workspace = true lyon_path.optional = true - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -twox-hash.workspace = true -twox-hash.features = ["std"] diff --git a/graphics/src/text/cache.rs b/graphics/src/text/cache.rs index b3293dd4..7fb33567 100644 --- a/graphics/src/text/cache.rs +++ b/graphics/src/text/cache.rs @@ -16,11 +16,7 @@ pub struct Cache { hasher: HashBuilder, } -#[cfg(not(target_arch = "wasm32"))] -type HashBuilder = twox_hash::RandomXxHashBuilder64; - -#[cfg(target_arch = "wasm32")] -type HashBuilder = std::hash::BuildHasherDefault; +type HashBuilder = xxhash_rust::xxh3::Xxh3Builder; impl Cache { /// Creates a new empty [`Cache`]. diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 15a6928a..df4c6143 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -26,11 +26,7 @@ raw-window-handle.workspace = true rustc-hash.workspace = true softbuffer.workspace = true tiny-skia.workspace = true -twox-hash.workspace = true +xxhash-rust.workspace = true resvg.workspace = true resvg.optional = true - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -twox-hash.workspace = true -twox-hash.features = ["std"] -- cgit From 4b69c71d5b570ce716b9c202e9a47d5ae9ce3ae0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 11 Nov 2023 03:43:03 +0100 Subject: Remove patch version from `xxhash-rust` dependency --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f625f1ad..4bcf7c7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,7 +150,7 @@ thiserror = "1.0" tiny-skia = "0.10" tokio = "1.0" tracing = "0.1" -xxhash-rust = { version = "0.8.7", default-features = false, features = ["xxh3"] } +xxhash-rust = { version = "0.8", default-features = false, features = ["xxh3"] } unicode-segmentation = "1.0" wasm-bindgen-futures = "0.4" wasm-timer = "0.2" -- cgit From 107e842071f1300df5e0bfcb26ee0a99024e51d8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 11 Nov 2023 03:43:50 +0100 Subject: Remove unnecessary `default-features` attribute from `xxhash-rust` dependency --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4bcf7c7a..ac34a4ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,7 +150,7 @@ thiserror = "1.0" tiny-skia = "0.10" tokio = "1.0" tracing = "0.1" -xxhash-rust = { version = "0.8", default-features = false, features = ["xxh3"] } +xxhash-rust = { version = "0.8", features = ["xxh3"] } unicode-segmentation = "1.0" wasm-bindgen-futures = "0.4" wasm-timer = "0.2" -- cgit From 2aaaf2cd0cb56f9efc946159a0232270f8d37eeb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 11 Nov 2023 04:03:25 +0100 Subject: Call `convert_text` on `svg` node before rendering `tiny-skia` does not support text rendering, so we convert the text nodes to path nodes prior to that. --- Cargo.toml | 4 ++-- tiny_skia/src/vector.rs | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1a286b9b..18dd8d3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -140,14 +140,14 @@ ouroboros = "0.17" palette = "0.7" qrcode = { version = "0.12", default-features = false } raw-window-handle = "0.5" -resvg = "0.35" +resvg = "0.36" rustc-hash = "1.0" smol = "1.0" softbuffer = "0.2" syntect = "5.1" sysinfo = "0.28" thiserror = "1.0" -tiny-skia = "0.10" +tiny-skia = "0.11" tokio = "1.0" tracing = "0.1" twox-hash = { version = "1.0", default-features = false } diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index a1cd269d..9c2893a2 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -1,7 +1,8 @@ use crate::core::svg::{Data, Handle}; use crate::core::{Color, Rectangle, Size}; +use crate::graphics::text; -use resvg::usvg; +use resvg::usvg::{self, TreeTextToPath}; use rustc_hash::{FxHashMap, FxHashSet}; use std::cell::RefCell; @@ -77,7 +78,7 @@ impl Cache { let id = handle.id(); if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) { - let svg = match handle.data() { + let mut svg = match handle.data() { Data::Path(path) => { fs::read_to_string(path).ok().and_then(|contents| { usvg::Tree::from_str( @@ -92,6 +93,15 @@ impl Cache { } }; + if let Some(svg) = &mut svg { + if svg.has_text_nodes() { + let mut font_system = + text::font_system().write().expect("Read font system"); + + svg.convert_text(font_system.raw().db_mut()); + } + } + let _ = entry.insert(svg); } -- cgit From bb2f557d6a75850aed8e8689348f7a544b364bf6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 11 Nov 2023 04:36:45 +0100 Subject: Fix `artifacts` job in `audit` workflow --- .github/workflows/audit.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index bfb617fb..5f5f7f65 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -24,5 +24,7 @@ jobs: - name: Install cargo-outdated run: cargo install cargo-outdated - uses: actions/checkout@master + - name: Delete `web-sys` dependency from `integration` example + run: sed '$d' examples/integration/Cargo.toml - name: Find outdated dependencies run: cargo outdated --workspace --exit-code 1 -- cgit From ef015a5e72802c059784e74d611f351df75403c0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 11 Nov 2023 04:46:11 +0100 Subject: Run `sed` with `-i` option in `artifacts` job --- .github/workflows/audit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 5f5f7f65..80bbcacd 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -25,6 +25,6 @@ jobs: run: cargo install cargo-outdated - uses: actions/checkout@master - name: Delete `web-sys` dependency from `integration` example - run: sed '$d' examples/integration/Cargo.toml + run: sed -i '$d' examples/integration/Cargo.toml - name: Find outdated dependencies run: cargo outdated --workspace --exit-code 1 -- cgit From 5759096a4c33935fcdf5f96606143e4f21159186 Mon Sep 17 00:00:00 2001 From: Remmirad Date: Wed, 31 May 2023 15:46:21 +0200 Subject: Implement texture filtering options --- core/src/image.rs | 32 +++++++++++++++++++++++ tiny_skia/src/raster.rs | 7 ++++- wgpu/src/image.rs | 69 ++++++++++++++++++++++++++++++++----------------- widget/src/image.rs | 2 +- 4 files changed, 84 insertions(+), 26 deletions(-) diff --git a/core/src/image.rs b/core/src/image.rs index 85d9d475..9a6011a3 100644 --- a/core/src/image.rs +++ b/core/src/image.rs @@ -5,11 +5,31 @@ use std::hash::{Hash, Hasher as _}; use std::path::PathBuf; use std::sync::Arc; +/// Image filter method +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum FilterMethod { + /// Bilinear interpolation + #[default] + Linear, + /// Nearest Neighbor + Nearest, +} + +/// Texture filter settings +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] +pub struct TextureFilter { + /// Filter for scaling the image down. + pub min: FilterMethod, + /// Filter for scaling the image up. + pub mag: FilterMethod, +} + /// A handle of some image data. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Handle { id: u64, data: Data, + filter: TextureFilter, } impl Handle { @@ -56,6 +76,7 @@ impl Handle { Handle { id: hasher.finish(), data, + filter: TextureFilter::default(), } } @@ -68,6 +89,17 @@ impl Handle { pub fn data(&self) -> &Data { &self.data } + + /// Returns a reference to the [`TextureFilter`]. + pub fn filter(&self) -> &TextureFilter { + &self.filter + } + + /// Sets the texture filtering methods. + pub fn set_filter(mut self, filter: TextureFilter) -> Self { + self.filter = filter; + self + } } impl From for Handle diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index d13b1167..95f74ad1 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -39,12 +39,17 @@ impl Pipeline { let transform = transform.pre_scale(width_scale, height_scale); + let quality = match handle.filter().mag { + raster::FilterMethod::Linear => tiny_skia::FilterQuality::Bilinear, + raster::FilterMethod::Nearest => tiny_skia::FilterQuality::Nearest, + }; + pixels.draw_pixmap( (bounds.x / width_scale) as i32, (bounds.y / height_scale) as i32, image, &tiny_skia::PixmapPaint { - quality: tiny_skia::FilterQuality::Bilinear, + quality: quality, ..Default::default() }, transform, diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 553ba330..a0fe7e83 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -7,6 +7,7 @@ mod raster; mod vector; use atlas::Atlas; +use iced_graphics::core::image::{TextureFilter, FilterMethod}; use crate::core::{Rectangle, Size}; use crate::graphics::Transformation; @@ -14,6 +15,7 @@ use crate::layer; use crate::Buffer; use std::cell::RefCell; +use std::collections::HashMap; use std::mem; use bytemuck::{Pod, Zeroable}; @@ -37,7 +39,7 @@ pub struct Pipeline { pipeline: wgpu::RenderPipeline, vertices: wgpu::Buffer, indices: wgpu::Buffer, - sampler: wgpu::Sampler, + sampler: HashMap, texture: wgpu::BindGroup, texture_version: usize, texture_atlas: Atlas, @@ -142,15 +144,32 @@ impl Pipeline { pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self { use wgpu::util::DeviceExt; - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, - ..Default::default() - }); + let to_wgpu = |method: FilterMethod| { + match method { + FilterMethod::Linear => wgpu::FilterMode::Linear, + FilterMethod::Nearest => wgpu::FilterMode::Nearest, + } + }; + + let mut sampler = HashMap::new(); + + let filter = [FilterMethod::Linear, FilterMethod::Nearest]; + for min in 0..filter.len() { + for mag in 0..filter.len() { + let _ = sampler.insert(TextureFilter {min: filter[min], mag: filter[mag]}, + device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: to_wgpu(filter[mag]), + min_filter: to_wgpu(filter[min]), + mipmap_filter: wgpu::FilterMode::Linear, + ..Default::default() + } + )); + } + } + let constant_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { @@ -355,7 +374,7 @@ impl Pipeline { #[cfg(feature = "tracing")] let _ = info_span!("Wgpu::Image", "DRAW").entered(); - let instances: &mut Vec = &mut Vec::new(); + let instances: &mut HashMap> = &mut HashMap::new(); #[cfg(feature = "image")] let mut raster_cache = self.raster_cache.borrow_mut(); @@ -377,7 +396,7 @@ impl Pipeline { [bounds.x, bounds.y], [bounds.width, bounds.height], atlas_entry, - instances, + instances.entry(handle.filter().clone()).or_insert(Vec::new()), ); } } @@ -405,7 +424,7 @@ impl Pipeline { [bounds.x, bounds.y], size, atlas_entry, - instances, + instances.entry(TextureFilter::default()).or_insert(Vec::new()), ); } } @@ -438,18 +457,20 @@ impl Pipeline { self.texture_version = texture_version; } - if self.layers.len() <= self.prepare_layer { - self.layers.push(Layer::new( - device, - &self.constant_layout, - &self.sampler, - )); + for (filter, instances) in instances.iter_mut() { + if self.layers.len() <= self.prepare_layer { + self.layers.push(Layer::new( + device, + &self.constant_layout, + &self.sampler.get(filter).expect("Sampler is registered"), + )); + } + + let layer = &mut self.layers[self.prepare_layer]; + layer.prepare(device, queue, &instances, transformation); + + self.prepare_layer += 1; } - - let layer = &mut self.layers[self.prepare_layer]; - layer.prepare(device, queue, instances, transformation); - - self.prepare_layer += 1; } pub fn render<'a>( diff --git a/widget/src/image.rs b/widget/src/image.rs index a0e89920..9f0b16b7 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -13,7 +13,7 @@ use crate::core::{ use std::hash::Hash; -pub use image::Handle; +pub use image::{Handle, TextureFilter, FilterMethod}; /// Creates a new [`Viewer`] with the given image `Handle`. pub fn viewer(handle: Handle) -> Viewer { -- cgit From 4b32a488808e371313ce78e727c9d98ab2eb759e Mon Sep 17 00:00:00 2001 From: Remmirad Date: Fri, 4 Aug 2023 13:50:16 +0200 Subject: Fix clippy + fmt --- core/src/image.rs | 2 +- tiny_skia/src/raster.rs | 10 +++++++--- wgpu/src/image.rs | 42 ++++++++++++++++++++++++------------------ widget/src/image.rs | 2 +- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/core/src/image.rs b/core/src/image.rs index 9a6011a3..69f19436 100644 --- a/core/src/image.rs +++ b/core/src/image.rs @@ -11,7 +11,7 @@ pub enum FilterMethod { /// Bilinear interpolation #[default] Linear, - /// Nearest Neighbor + /// Nearest Neighbor Nearest, } diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index 95f74ad1..3f35ee78 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -40,8 +40,12 @@ impl Pipeline { let transform = transform.pre_scale(width_scale, height_scale); let quality = match handle.filter().mag { - raster::FilterMethod::Linear => tiny_skia::FilterQuality::Bilinear, - raster::FilterMethod::Nearest => tiny_skia::FilterQuality::Nearest, + raster::FilterMethod::Linear => { + tiny_skia::FilterQuality::Bilinear + } + raster::FilterMethod::Nearest => { + tiny_skia::FilterQuality::Nearest + } }; pixels.draw_pixmap( @@ -49,7 +53,7 @@ impl Pipeline { (bounds.y / height_scale) as i32, image, &tiny_skia::PixmapPaint { - quality: quality, + quality, ..Default::default() }, transform, diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index a0fe7e83..a3168001 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -7,7 +7,7 @@ mod raster; mod vector; use atlas::Atlas; -use iced_graphics::core::image::{TextureFilter, FilterMethod}; +use iced_graphics::core::image::{FilterMethod, TextureFilter}; use crate::core::{Rectangle, Size}; use crate::graphics::Transformation; @@ -39,7 +39,7 @@ pub struct Pipeline { pipeline: wgpu::RenderPipeline, vertices: wgpu::Buffer, indices: wgpu::Buffer, - sampler: HashMap, + sampler: HashMap, texture: wgpu::BindGroup, texture_version: usize, texture_atlas: Atlas, @@ -144,11 +144,9 @@ impl Pipeline { pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self { use wgpu::util::DeviceExt; - let to_wgpu = |method: FilterMethod| { - match method { - FilterMethod::Linear => wgpu::FilterMode::Linear, - FilterMethod::Nearest => wgpu::FilterMode::Nearest, - } + let to_wgpu = |method: FilterMethod| match method { + FilterMethod::Linear => wgpu::FilterMode::Linear, + FilterMethod::Nearest => wgpu::FilterMode::Nearest, }; let mut sampler = HashMap::new(); @@ -156,7 +154,11 @@ impl Pipeline { let filter = [FilterMethod::Linear, FilterMethod::Nearest]; for min in 0..filter.len() { for mag in 0..filter.len() { - let _ = sampler.insert(TextureFilter {min: filter[min], mag: filter[mag]}, + let _ = sampler.insert( + TextureFilter { + min: filter[min], + mag: filter[mag], + }, device.create_sampler(&wgpu::SamplerDescriptor { address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, @@ -165,12 +167,11 @@ impl Pipeline { min_filter: to_wgpu(filter[min]), mipmap_filter: wgpu::FilterMode::Linear, ..Default::default() - } - )); + }), + ); } } - let constant_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("iced_wgpu::image constants layout"), @@ -374,7 +375,8 @@ impl Pipeline { #[cfg(feature = "tracing")] let _ = info_span!("Wgpu::Image", "DRAW").entered(); - let instances: &mut HashMap> = &mut HashMap::new(); + let instances: &mut HashMap> = + &mut HashMap::new(); #[cfg(feature = "image")] let mut raster_cache = self.raster_cache.borrow_mut(); @@ -396,7 +398,9 @@ impl Pipeline { [bounds.x, bounds.y], [bounds.width, bounds.height], atlas_entry, - instances.entry(handle.filter().clone()).or_insert(Vec::new()), + instances + .entry(handle.filter().clone()) + .or_insert(Vec::new()), ); } } @@ -424,7 +428,9 @@ impl Pipeline { [bounds.x, bounds.y], size, atlas_entry, - instances.entry(TextureFilter::default()).or_insert(Vec::new()), + instances + .entry(TextureFilter::default()) + .or_insert(Vec::new()), ); } } @@ -462,13 +468,13 @@ impl Pipeline { self.layers.push(Layer::new( device, &self.constant_layout, - &self.sampler.get(filter).expect("Sampler is registered"), + self.sampler.get(filter).expect("Sampler is registered"), )); } - + let layer = &mut self.layers[self.prepare_layer]; - layer.prepare(device, queue, &instances, transformation); - + layer.prepare(device, queue, instances, transformation); + self.prepare_layer += 1; } } diff --git a/widget/src/image.rs b/widget/src/image.rs index 9f0b16b7..684f200c 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -13,7 +13,7 @@ use crate::core::{ use std::hash::Hash; -pub use image::{Handle, TextureFilter, FilterMethod}; +pub use image::{FilterMethod, Handle, TextureFilter}; /// Creates a new [`Viewer`] with the given image `Handle`. pub fn viewer(handle: Handle) -> Viewer { -- cgit From e5d3e75d826e9fad8a0da5dd538aa542059dd034 Mon Sep 17 00:00:00 2001 From: Remmirad Date: Mon, 25 Sep 2023 21:54:50 +0200 Subject: fix design for wgpu backend --- wgpu/src/image.rs | 133 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 77 insertions(+), 56 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index a3168001..0aa7f899 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -8,6 +8,7 @@ mod vector; use atlas::Atlas; use iced_graphics::core::image::{FilterMethod, TextureFilter}; +use wgpu::Sampler; use crate::core::{Rectangle, Size}; use crate::graphics::Transformation; @@ -15,7 +16,6 @@ use crate::layer; use crate::Buffer; use std::cell::RefCell; -use std::collections::HashMap; use std::mem; use bytemuck::{Pod, Zeroable}; @@ -29,6 +29,8 @@ use crate::core::svg; #[cfg(feature = "tracing")] use tracing::info_span; +const SAMPLER_COUNT: usize = 4; + #[derive(Debug)] pub struct Pipeline { #[cfg(feature = "image")] @@ -39,14 +41,14 @@ pub struct Pipeline { pipeline: wgpu::RenderPipeline, vertices: wgpu::Buffer, indices: wgpu::Buffer, - sampler: HashMap, + sampler: [wgpu::Sampler; SAMPLER_COUNT], texture: wgpu::BindGroup, texture_version: usize, texture_atlas: Atlas, texture_layout: wgpu::BindGroupLayout, constant_layout: wgpu::BindGroupLayout, - layers: Vec, + layers: Vec<[Option; SAMPLER_COUNT]>, prepare_layer: usize, } @@ -149,28 +151,32 @@ impl Pipeline { FilterMethod::Nearest => wgpu::FilterMode::Nearest, }; - let mut sampler = HashMap::new(); + let mut sampler: [Option; SAMPLER_COUNT] = + [None, None, None, None]; let filter = [FilterMethod::Linear, FilterMethod::Nearest]; for min in 0..filter.len() { for mag in 0..filter.len() { - let _ = sampler.insert( - TextureFilter { - min: filter[min], - mag: filter[mag], - }, - device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: to_wgpu(filter[mag]), - min_filter: to_wgpu(filter[min]), - mipmap_filter: wgpu::FilterMode::Linear, - ..Default::default() - }), - ); + sampler[to_index(&TextureFilter { + min: filter[min], + mag: filter[mag], + })] = Some(device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: to_wgpu(filter[mag]), + min_filter: to_wgpu(filter[min]), + mipmap_filter: wgpu::FilterMode::Linear, + ..Default::default() + })); } } + let sampler = [ + sampler[0].take().unwrap(), + sampler[1].take().unwrap(), + sampler[2].take().unwrap(), + sampler[3].take().unwrap(), + ]; let constant_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { @@ -375,8 +381,8 @@ impl Pipeline { #[cfg(feature = "tracing")] let _ = info_span!("Wgpu::Image", "DRAW").entered(); - let instances: &mut HashMap> = - &mut HashMap::new(); + let mut instances: [Vec; SAMPLER_COUNT] = + [Vec::new(), Vec::new(), Vec::new(), Vec::new()]; #[cfg(feature = "image")] let mut raster_cache = self.raster_cache.borrow_mut(); @@ -398,9 +404,7 @@ impl Pipeline { [bounds.x, bounds.y], [bounds.width, bounds.height], atlas_entry, - instances - .entry(handle.filter().clone()) - .or_insert(Vec::new()), + &mut instances[to_index(handle.filter())], ); } } @@ -428,9 +432,7 @@ impl Pipeline { [bounds.x, bounds.y], size, atlas_entry, - instances - .entry(TextureFilter::default()) - .or_insert(Vec::new()), + &mut instances[to_index(&TextureFilter::default())], ); } } @@ -463,20 +465,26 @@ impl Pipeline { self.texture_version = texture_version; } - for (filter, instances) in instances.iter_mut() { - if self.layers.len() <= self.prepare_layer { - self.layers.push(Layer::new( - device, - &self.constant_layout, - self.sampler.get(filter).expect("Sampler is registered"), - )); + if self.layers.len() <= self.prepare_layer { + self.layers.push([None, None, None, None]); + } + for (i, instances) in instances.iter_mut().enumerate() { + let layer = &mut self.layers[self.prepare_layer][i]; + if !instances.is_empty() { + if layer.is_none() { + *layer = Some(Layer::new( + device, + &self.constant_layout, + &self.sampler[i], + )) + } } - let layer = &mut self.layers[self.prepare_layer]; - layer.prepare(device, queue, instances, transformation); - - self.prepare_layer += 1; + if let Some(layer) = layer { + layer.prepare(device, queue, instances, transformation); + } } + self.prepare_layer += 1; } pub fn render<'a>( @@ -485,24 +493,29 @@ impl Pipeline { bounds: Rectangle, render_pass: &mut wgpu::RenderPass<'a>, ) { - if let Some(layer) = self.layers.get(layer) { - render_pass.set_pipeline(&self.pipeline); - - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); - - render_pass.set_bind_group(1, &self.texture, &[]); - render_pass.set_index_buffer( - self.indices.slice(..), - wgpu::IndexFormat::Uint16, - ); - render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - - layer.render(render_pass); + if let Some(layer_group) = self.layers.get(layer) { + for (i, layer) in layer_group.iter().enumerate() { + if let Some(layer) = layer { + println!("Render {i}"); + render_pass.set_pipeline(&self.pipeline); + + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + + render_pass.set_bind_group(1, &self.texture, &[]); + render_pass.set_index_buffer( + self.indices.slice(..), + wgpu::IndexFormat::Uint16, + ); + render_pass.set_vertex_buffer(0, self.vertices.slice(..)); + + layer.render(render_pass); + } + } } } @@ -517,6 +530,14 @@ impl Pipeline { } } +fn to_index(filter: &TextureFilter) -> usize { + let to_index = |m| match m { + FilterMethod::Linear => 0, + FilterMethod::Nearest => 1, + }; + return (to_index(filter.mag) << 1) | (to_index(filter.min)); +} + #[repr(C)] #[derive(Clone, Copy, Zeroable, Pod)] pub struct Vertex { -- cgit From 75c9afc608a4a9ff44d60a8fb6f4a5819f05bf79 Mon Sep 17 00:00:00 2001 From: Remmirad Date: Mon, 25 Sep 2023 22:03:22 +0200 Subject: Remove debug traces --- wgpu/src/image.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 0aa7f899..6768a714 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -494,9 +494,8 @@ impl Pipeline { render_pass: &mut wgpu::RenderPass<'a>, ) { if let Some(layer_group) = self.layers.get(layer) { - for (i, layer) in layer_group.iter().enumerate() { + for layer in layer_group.iter() { if let Some(layer) = layer { - println!("Render {i}"); render_pass.set_pipeline(&self.pipeline); render_pass.set_scissor_rect( -- cgit From a5125d6fea824df1191777fe3eb53a2f748208b9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 11 Nov 2023 07:02:01 +0100 Subject: Refactor texture image filtering - Support only `Linear` or `Nearest` - Simplify `Layer` groups - Move `FilterMethod` to `Image` and `image::Viewer` --- core/src/image.rs | 49 +++------- examples/tour/src/main.rs | 44 +++++++-- graphics/src/primitive.rs | 2 + graphics/src/renderer.rs | 13 ++- renderer/src/lib.rs | 9 +- tiny_skia/src/backend.rs | 16 ++- tiny_skia/src/raster.rs | 3 +- wgpu/src/image.rs | 238 +++++++++++++++++++++++++-------------------- wgpu/src/layer.rs | 7 +- wgpu/src/layer/image.rs | 3 + widget/src/image.rs | 29 ++++-- widget/src/image/viewer.rs | 5 +- 12 files changed, 254 insertions(+), 164 deletions(-) diff --git a/core/src/image.rs b/core/src/image.rs index 69f19436..e9675316 100644 --- a/core/src/image.rs +++ b/core/src/image.rs @@ -5,31 +5,11 @@ use std::hash::{Hash, Hasher as _}; use std::path::PathBuf; use std::sync::Arc; -/// Image filter method -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum FilterMethod { - /// Bilinear interpolation - #[default] - Linear, - /// Nearest Neighbor - Nearest, -} - -/// Texture filter settings -#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] -pub struct TextureFilter { - /// Filter for scaling the image down. - pub min: FilterMethod, - /// Filter for scaling the image up. - pub mag: FilterMethod, -} - /// A handle of some image data. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Handle { id: u64, data: Data, - filter: TextureFilter, } impl Handle { @@ -76,7 +56,6 @@ impl Handle { Handle { id: hasher.finish(), data, - filter: TextureFilter::default(), } } @@ -89,17 +68,6 @@ impl Handle { pub fn data(&self) -> &Data { &self.data } - - /// Returns a reference to the [`TextureFilter`]. - pub fn filter(&self) -> &TextureFilter { - &self.filter - } - - /// Sets the texture filtering methods. - pub fn set_filter(mut self, filter: TextureFilter) -> Self { - self.filter = filter; - self - } } impl From for Handle @@ -196,6 +164,16 @@ impl std::fmt::Debug for Data { } } +/// Image filtering strategy. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum FilterMethod { + /// Bilinear interpolation. + #[default] + Linear, + /// Nearest neighbor. + Nearest, +} + /// A [`Renderer`] that can render raster graphics. /// /// [renderer]: crate::renderer @@ -210,5 +188,10 @@ pub trait Renderer: crate::Renderer { /// Draws an image with the given [`Handle`] and inside the provided /// `bounds`. - fn draw(&mut self, handle: Self::Handle, bounds: Rectangle); + fn draw( + &mut self, + handle: Self::Handle, + filter_method: FilterMethod, + bounds: Rectangle, + ); } diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index d46e40d1..7003d8ae 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -1,4 +1,4 @@ -use iced::alignment; +use iced::alignment::{self, Alignment}; use iced::theme; use iced::widget::{ checkbox, column, container, horizontal_space, image, radio, row, @@ -126,7 +126,10 @@ impl Steps { Step::Toggler { can_continue: false, }, - Step::Image { width: 300 }, + Step::Image { + width: 300, + filter_method: image::FilterMethod::Linear, + }, Step::Scrollable, Step::TextInput { value: String::new(), @@ -195,6 +198,7 @@ enum Step { }, Image { width: u16, + filter_method: image::FilterMethod, }, Scrollable, TextInput { @@ -215,6 +219,7 @@ pub enum StepMessage { TextColorChanged(Color), LanguageSelected(Language), ImageWidthChanged(u16), + ImageUseNearestToggled(bool), InputChanged(String), ToggleSecureInput(bool), ToggleTextInputIcon(bool), @@ -265,6 +270,15 @@ impl<'a> Step { *width = new_width; } } + StepMessage::ImageUseNearestToggled(use_nearest) => { + if let Step::Image { filter_method, .. } = self { + *filter_method = if use_nearest { + image::FilterMethod::Nearest + } else { + image::FilterMethod::Linear + }; + } + } StepMessage::InputChanged(new_value) => { if let Step::TextInput { value, .. } = self { *value = new_value; @@ -330,7 +344,10 @@ impl<'a> Step { Step::Toggler { can_continue } => Self::toggler(*can_continue), Step::Slider { value } => Self::slider(*value), Step::Text { size, color } => Self::text(*size, *color), - Step::Image { width } => Self::image(*width), + Step::Image { + width, + filter_method, + } => Self::image(*width, *filter_method), Step::RowsAndColumns { layout, spacing } => { Self::rows_and_columns(*layout, *spacing) } @@ -525,16 +542,25 @@ impl<'a> Step { ) } - fn image(width: u16) -> Column<'a, StepMessage> { + fn image( + width: u16, + filter_method: image::FilterMethod, + ) -> Column<'a, StepMessage> { Self::container("Image") .push("An image that tries to keep its aspect ratio.") - .push(ferris(width)) + .push(ferris(width, filter_method)) .push(slider(100..=500, width, StepMessage::ImageWidthChanged)) .push( text(format!("Width: {width} px")) .width(Length::Fill) .horizontal_alignment(alignment::Horizontal::Center), ) + .push(checkbox( + "Use nearest interpolation", + filter_method == image::FilterMethod::Nearest, + StepMessage::ImageUseNearestToggled, + )) + .align_items(Alignment::Center) } fn scrollable() -> Column<'a, StepMessage> { @@ -555,7 +581,7 @@ impl<'a> Step { .horizontal_alignment(alignment::Horizontal::Center), ) .push(vertical_space(4096)) - .push(ferris(300)) + .push(ferris(300, image::FilterMethod::Linear)) .push( text("You made it!") .width(Length::Fill) @@ -646,7 +672,10 @@ impl<'a> Step { } } -fn ferris<'a>(width: u16) -> Container<'a, StepMessage> { +fn ferris<'a>( + width: u16, + filter_method: image::FilterMethod, +) -> Container<'a, StepMessage> { container( // This should go away once we unify resource loading on native // platforms @@ -655,6 +684,7 @@ fn ferris<'a>(width: u16) -> Container<'a, StepMessage> { } else { image(format!("{}/images/ferris.png", env!("CARGO_MANIFEST_DIR"))) } + .filter_method(filter_method) .width(width), ) .width(Length::Fill) diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index ce0b734b..4ed512c1 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -68,6 +68,8 @@ pub enum Primitive { Image { /// The handle of the image handle: image::Handle, + /// The filter method of the image + filter_method: image::FilterMethod, /// The bounds of the image bounds: Rectangle, }, diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 93fff3b7..d7613e36 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -215,8 +215,17 @@ where self.backend().dimensions(handle) } - fn draw(&mut self, handle: image::Handle, bounds: Rectangle) { - self.primitives.push(Primitive::Image { handle, bounds }); + fn draw( + &mut self, + handle: image::Handle, + filter_method: image::FilterMethod, + bounds: Rectangle, + ) { + self.primitives.push(Primitive::Image { + handle, + filter_method, + bounds, + }); } } diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index cc81c6e2..43f9794b 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -214,8 +214,13 @@ impl crate::core::image::Renderer for Renderer { delegate!(self, renderer, renderer.dimensions(handle)) } - fn draw(&mut self, handle: crate::core::image::Handle, bounds: Rectangle) { - delegate!(self, renderer, renderer.draw(handle, bounds)); + fn draw( + &mut self, + handle: crate::core::image::Handle, + filter_method: crate::core::image::FilterMethod, + bounds: Rectangle, + ) { + delegate!(self, renderer, renderer.draw(handle, filter_method, bounds)); } } diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 3c6fe288..f2905b00 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -445,7 +445,11 @@ impl Backend { ); } #[cfg(feature = "image")] - Primitive::Image { handle, bounds } => { + Primitive::Image { + handle, + filter_method, + bounds, + } => { let physical_bounds = (*bounds + translation) * scale_factor; if !clip_bounds.intersects(&physical_bounds) { @@ -461,8 +465,14 @@ impl Backend { ) .post_scale(scale_factor, scale_factor); - self.raster_pipeline - .draw(handle, *bounds, pixels, transform, clip_mask); + self.raster_pipeline.draw( + handle, + *filter_method, + *bounds, + pixels, + transform, + clip_mask, + ); } #[cfg(not(feature = "image"))] Primitive::Image { .. } => { diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index 3f35ee78..5f17ae60 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -28,6 +28,7 @@ impl Pipeline { pub fn draw( &mut self, handle: &raster::Handle, + filter_method: raster::FilterMethod, bounds: Rectangle, pixels: &mut tiny_skia::PixmapMut<'_>, transform: tiny_skia::Transform, @@ -39,7 +40,7 @@ impl Pipeline { let transform = transform.pre_scale(width_scale, height_scale); - let quality = match handle.filter().mag { + let quality = match filter_method { raster::FilterMethod::Linear => { tiny_skia::FilterQuality::Bilinear } diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 6768a714..1a88c6ae 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -7,8 +7,6 @@ mod raster; mod vector; use atlas::Atlas; -use iced_graphics::core::image::{FilterMethod, TextureFilter}; -use wgpu::Sampler; use crate::core::{Rectangle, Size}; use crate::graphics::Transformation; @@ -29,8 +27,6 @@ use crate::core::svg; #[cfg(feature = "tracing")] use tracing::info_span; -const SAMPLER_COUNT: usize = 4; - #[derive(Debug)] pub struct Pipeline { #[cfg(feature = "image")] @@ -41,30 +37,31 @@ pub struct Pipeline { pipeline: wgpu::RenderPipeline, vertices: wgpu::Buffer, indices: wgpu::Buffer, - sampler: [wgpu::Sampler; SAMPLER_COUNT], + nearest_sampler: wgpu::Sampler, + linear_sampler: wgpu::Sampler, texture: wgpu::BindGroup, texture_version: usize, texture_atlas: Atlas, texture_layout: wgpu::BindGroupLayout, constant_layout: wgpu::BindGroupLayout, - layers: Vec<[Option; SAMPLER_COUNT]>, + layers: Vec, prepare_layer: usize, } #[derive(Debug)] struct Layer { uniforms: wgpu::Buffer, - constants: wgpu::BindGroup, - instances: Buffer, - instance_count: usize, + nearest: Data, + linear: Data, } impl Layer { fn new( device: &wgpu::Device, constant_layout: &wgpu::BindGroupLayout, - sampler: &wgpu::Sampler, + nearest_sampler: &wgpu::Sampler, + linear_sampler: &wgpu::Sampler, ) -> Self { let uniforms = device.create_buffer(&wgpu::BufferDescriptor { label: Some("iced_wgpu::image uniforms buffer"), @@ -73,6 +70,59 @@ impl Layer { mapped_at_creation: false, }); + let nearest = + Data::new(device, constant_layout, nearest_sampler, &uniforms); + + let linear = + Data::new(device, constant_layout, linear_sampler, &uniforms); + + Self { + uniforms, + nearest, + linear, + } + } + + fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + nearest_instances: &[Instance], + linear_instances: &[Instance], + transformation: Transformation, + ) { + queue.write_buffer( + &self.uniforms, + 0, + bytemuck::bytes_of(&Uniforms { + transform: transformation.into(), + }), + ); + + self.nearest.upload(device, queue, nearest_instances); + self.linear.upload(device, queue, linear_instances); + } + + fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { + self.nearest.render(render_pass); + self.linear.render(render_pass); + } +} + +#[derive(Debug)] +struct Data { + constants: wgpu::BindGroup, + instances: Buffer, + instance_count: usize, +} + +impl Data { + pub fn new( + device: &wgpu::Device, + constant_layout: &wgpu::BindGroupLayout, + sampler: &wgpu::Sampler, + uniforms: &wgpu::Buffer, + ) -> Self { let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("iced_wgpu::image constants bind group"), layout: constant_layout, @@ -102,28 +152,18 @@ impl Layer { ); Self { - uniforms, constants, instances, instance_count: 0, } } - fn prepare( + fn upload( &mut self, device: &wgpu::Device, queue: &wgpu::Queue, instances: &[Instance], - transformation: Transformation, ) { - queue.write_buffer( - &self.uniforms, - 0, - bytemuck::bytes_of(&Uniforms { - transform: transformation.into(), - }), - ); - let _ = self.instances.resize(device, instances.len()); let _ = self.instances.write(queue, 0, instances); @@ -146,37 +186,25 @@ impl Pipeline { pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self { use wgpu::util::DeviceExt; - let to_wgpu = |method: FilterMethod| match method { - FilterMethod::Linear => wgpu::FilterMode::Linear, - FilterMethod::Nearest => wgpu::FilterMode::Nearest, - }; - - let mut sampler: [Option; SAMPLER_COUNT] = - [None, None, None, None]; - - let filter = [FilterMethod::Linear, FilterMethod::Nearest]; - for min in 0..filter.len() { - for mag in 0..filter.len() { - sampler[to_index(&TextureFilter { - min: filter[min], - mag: filter[mag], - })] = Some(device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: to_wgpu(filter[mag]), - min_filter: to_wgpu(filter[min]), - mipmap_filter: wgpu::FilterMode::Linear, - ..Default::default() - })); - } - } - let sampler = [ - sampler[0].take().unwrap(), - sampler[1].take().unwrap(), - sampler[2].take().unwrap(), - sampler[3].take().unwrap(), - ]; + let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + min_filter: wgpu::FilterMode::Nearest, + mag_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + min_filter: wgpu::FilterMode::Linear, + mag_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); let constant_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { @@ -338,7 +366,8 @@ impl Pipeline { pipeline, vertices, indices, - sampler, + nearest_sampler, + linear_sampler, texture, texture_version: texture_atlas.layer_count(), texture_atlas, @@ -381,8 +410,8 @@ impl Pipeline { #[cfg(feature = "tracing")] let _ = info_span!("Wgpu::Image", "DRAW").entered(); - let mut instances: [Vec; SAMPLER_COUNT] = - [Vec::new(), Vec::new(), Vec::new(), Vec::new()]; + let nearest_instances: &mut Vec = &mut Vec::new(); + let linear_instances: &mut Vec = &mut Vec::new(); #[cfg(feature = "image")] let mut raster_cache = self.raster_cache.borrow_mut(); @@ -393,7 +422,11 @@ impl Pipeline { for image in images { match &image { #[cfg(feature = "image")] - layer::Image::Raster { handle, bounds } => { + layer::Image::Raster { + handle, + filter_method, + bounds, + } => { if let Some(atlas_entry) = raster_cache.upload( device, encoder, @@ -404,7 +437,12 @@ impl Pipeline { [bounds.x, bounds.y], [bounds.width, bounds.height], atlas_entry, - &mut instances[to_index(handle.filter())], + match filter_method { + image::FilterMethod::Nearest => { + nearest_instances + } + image::FilterMethod::Linear => linear_instances, + }, ); } } @@ -432,7 +470,7 @@ impl Pipeline { [bounds.x, bounds.y], size, atlas_entry, - &mut instances[to_index(&TextureFilter::default())], + nearest_instances, ); } } @@ -441,7 +479,7 @@ impl Pipeline { } } - if instances.is_empty() { + if nearest_instances.is_empty() && linear_instances.is_empty() { return; } @@ -466,24 +504,24 @@ impl Pipeline { } if self.layers.len() <= self.prepare_layer { - self.layers.push([None, None, None, None]); + self.layers.push(Layer::new( + device, + &self.constant_layout, + &self.nearest_sampler, + &self.linear_sampler, + )); } - for (i, instances) in instances.iter_mut().enumerate() { - let layer = &mut self.layers[self.prepare_layer][i]; - if !instances.is_empty() { - if layer.is_none() { - *layer = Some(Layer::new( - device, - &self.constant_layout, - &self.sampler[i], - )) - } - } - if let Some(layer) = layer { - layer.prepare(device, queue, instances, transformation); - } - } + let layer = &mut self.layers[self.prepare_layer]; + + layer.prepare( + device, + queue, + &nearest_instances, + &linear_instances, + transformation, + ); + self.prepare_layer += 1; } @@ -493,28 +531,24 @@ impl Pipeline { bounds: Rectangle, render_pass: &mut wgpu::RenderPass<'a>, ) { - if let Some(layer_group) = self.layers.get(layer) { - for layer in layer_group.iter() { - if let Some(layer) = layer { - render_pass.set_pipeline(&self.pipeline); - - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); - - render_pass.set_bind_group(1, &self.texture, &[]); - render_pass.set_index_buffer( - self.indices.slice(..), - wgpu::IndexFormat::Uint16, - ); - render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - - layer.render(render_pass); - } - } + if let Some(layer) = self.layers.get(layer) { + render_pass.set_pipeline(&self.pipeline); + + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + + render_pass.set_bind_group(1, &self.texture, &[]); + render_pass.set_index_buffer( + self.indices.slice(..), + wgpu::IndexFormat::Uint16, + ); + render_pass.set_vertex_buffer(0, self.vertices.slice(..)); + + layer.render(render_pass); } } @@ -529,14 +563,6 @@ impl Pipeline { } } -fn to_index(filter: &TextureFilter) -> usize { - let to_index = |m| match m { - FilterMethod::Linear => 0, - FilterMethod::Nearest => 1, - }; - return (to_index(filter.mag) << 1) | (to_index(filter.min)); -} - #[repr(C)] #[derive(Clone, Copy, Zeroable, Pod)] pub struct Vertex { @@ -571,7 +597,7 @@ struct Instance { } impl Instance { - pub const INITIAL: usize = 1_000; + pub const INITIAL: usize = 20; } #[repr(C)] diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index b251538e..286801e6 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -186,11 +186,16 @@ impl<'a> Layer<'a> { layer.quads.add(quad, background); } - Primitive::Image { handle, bounds } => { + Primitive::Image { + handle, + filter_method, + bounds, + } => { let layer = &mut layers[current_layer]; layer.images.push(Image::Raster { handle: handle.clone(), + filter_method: *filter_method, bounds: *bounds + translation, }); } diff --git a/wgpu/src/layer/image.rs b/wgpu/src/layer/image.rs index 0de589f8..facbe192 100644 --- a/wgpu/src/layer/image.rs +++ b/wgpu/src/layer/image.rs @@ -10,6 +10,9 @@ pub enum Image { /// The handle of a raster image. handle: image::Handle, + /// The filter method of a raster image. + filter_method: image::FilterMethod, + /// The bounds of the image. bounds: Rectangle, }, diff --git a/widget/src/image.rs b/widget/src/image.rs index 684f200c..67699102 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -13,7 +13,7 @@ use crate::core::{ use std::hash::Hash; -pub use image::{FilterMethod, Handle, TextureFilter}; +pub use image::{FilterMethod, Handle}; /// Creates a new [`Viewer`] with the given image `Handle`. pub fn viewer(handle: Handle) -> Viewer { @@ -37,6 +37,7 @@ pub struct Image { width: Length, height: Length, content_fit: ContentFit, + filter_method: FilterMethod, } impl Image { @@ -47,6 +48,7 @@ impl Image { width: Length::Shrink, height: Length::Shrink, content_fit: ContentFit::Contain, + filter_method: FilterMethod::default(), } } @@ -65,11 +67,15 @@ impl Image { /// Sets the [`ContentFit`] of the [`Image`]. /// /// Defaults to [`ContentFit::Contain`] - pub fn content_fit(self, content_fit: ContentFit) -> Self { - Self { - content_fit, - ..self - } + pub fn content_fit(mut self, content_fit: ContentFit) -> Self { + self.content_fit = content_fit; + self + } + + /// Sets the [`FilterMethod`] of the [`Image`]. + pub fn filter_method(mut self, filter_method: FilterMethod) -> Self { + self.filter_method = filter_method; + self } } @@ -119,6 +125,7 @@ pub fn draw( layout: Layout<'_>, handle: &Handle, content_fit: ContentFit, + filter_method: FilterMethod, ) where Renderer: image::Renderer, Handle: Clone + Hash, @@ -141,7 +148,7 @@ pub fn draw( ..bounds }; - renderer.draw(handle.clone(), drawing_bounds + offset); + renderer.draw(handle.clone(), filter_method, drawing_bounds + offset); }; if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height @@ -191,7 +198,13 @@ where _cursor: mouse::Cursor, _viewport: &Rectangle, ) { - draw(renderer, layout, &self.handle, self.content_fit); + draw( + renderer, + layout, + &self.handle, + self.content_fit, + self.filter_method, + ); } } diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 44624fc8..68015ba8 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -22,19 +22,21 @@ pub struct Viewer { max_scale: f32, scale_step: f32, handle: Handle, + filter_method: image::FilterMethod, } impl Viewer { /// Creates a new [`Viewer`] with the given [`State`]. pub fn new(handle: Handle) -> Self { Viewer { + handle, padding: 0.0, width: Length::Shrink, height: Length::Shrink, min_scale: 0.25, max_scale: 10.0, scale_step: 0.10, - handle, + filter_method: image::FilterMethod::default(), } } @@ -329,6 +331,7 @@ where image::Renderer::draw( renderer, self.handle.clone(), + self.filter_method, Rectangle { x: bounds.x, y: bounds.y, -- cgit From 9d560c813566ba04be3e23ae1b14861365485b57 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 11 Nov 2023 07:27:38 +0100 Subject: Fix unnecessary references in `iced_wgpu::image` --- wgpu/src/image.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 1a88c6ae..b78802c7 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -131,7 +131,7 @@ impl Data { binding: 0, resource: wgpu::BindingResource::Buffer( wgpu::BufferBinding { - buffer: &uniforms, + buffer: uniforms, offset: 0, size: None, }, @@ -517,8 +517,8 @@ impl Pipeline { layer.prepare( device, queue, - &nearest_instances, - &linear_instances, + nearest_instances, + linear_instances, transformation, ); -- cgit From ae2d59ae96ba1ec2daf6979beff0e3913ba9e0b8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 12 Nov 2023 03:17:02 +0100 Subject: Add `check` workflow to ensure `iced_widget` crate compiles --- .github/workflows/check.yml | 29 +++++++++++++++++++++++++++++ .github/workflows/test.yml | 21 +-------------------- 2 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/check.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 00000000..df9c480f --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,29 @@ +name: Check +on: [push, pull_request] +jobs: + widget: + runs-on: ubuntu-latest + steps: + - uses: hecrj/setup-rust-action@v1 + - uses: actions/checkout@master + - name: Check standalone `iced_widget` crate + run: cargo check --package iced_widget --features image,svg,canvas + + wasm: + runs-on: ubuntu-latest + env: + RUSTFLAGS: --cfg=web_sys_unstable_apis + steps: + - uses: hecrj/setup-rust-action@v1 + with: + rust-version: stable + targets: wasm32-unknown-unknown + - uses: actions/checkout@master + - name: Run checks + run: cargo check --package iced --target wasm32-unknown-unknown + - name: Check compilation of `tour` example + run: cargo build --package tour --target wasm32-unknown-unknown + - name: Check compilation of `todos` example + run: cargo build --package todos --target wasm32-unknown-unknown + - name: Check compilation of `integration` example + run: cargo build --package integration --target wasm32-unknown-unknown diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 215b616b..a08033c9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,7 @@ name: Test on: [push, pull_request] jobs: - native: + all: runs-on: ${{ matrix.os }} strategy: matrix: @@ -22,22 +22,3 @@ jobs: run: | cargo test --verbose --workspace cargo test --verbose --workspace --all-features - - web: - runs-on: ubuntu-latest - env: - RUSTFLAGS: --cfg=web_sys_unstable_apis - steps: - - uses: hecrj/setup-rust-action@v1 - with: - rust-version: stable - targets: wasm32-unknown-unknown - - uses: actions/checkout@master - - name: Run checks - run: cargo check --package iced --target wasm32-unknown-unknown - - name: Check compilation of `tour` example - run: cargo build --package tour --target wasm32-unknown-unknown - - name: Check compilation of `todos` example - run: cargo build --package todos --target wasm32-unknown-unknown - - name: Check compilation of `integration` example - run: cargo build --package integration --target wasm32-unknown-unknown -- cgit From 9d5ff12063e05158ede74f1aec4167bf910c8730 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 12 Nov 2023 03:22:43 +0100 Subject: Fix conditional compilation in `iced_renderer` --- renderer/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 43f9794b..78dec847 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -252,6 +252,7 @@ impl crate::graphics::geometry::Renderer for Renderer { crate::Geometry::TinySkia(primitive) => { renderer.draw_primitive(primitive); } + #[cfg(feature = "wgpu")] crate::Geometry::Wgpu(_) => unreachable!(), } } -- cgit From 93416cbebd1dad04d250bc39ee7db9482d1e5e72 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 12 Nov 2023 03:33:09 +0100 Subject: Deny warnings in `test` workflow --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 215b616b..e9e1d86b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,6 +3,8 @@ on: [push, pull_request] jobs: native: runs-on: ${{ matrix.os }} + env: + RUSTFLAGS: --deny warnings strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] -- cgit From f98627a317615151681ca8b324052eb4a170b789 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 12 Nov 2023 03:40:32 +0100 Subject: Add missing `'static` lifetimes to constant slices --- examples/lazy/src/main.rs | 2 +- examples/modal/src/main.rs | 3 ++- examples/toast/src/main.rs | 2 +- highlighter/src/lib.rs | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs index 9bf17c56..01560598 100644 --- a/examples/lazy/src/main.rs +++ b/examples/lazy/src/main.rs @@ -46,7 +46,7 @@ enum Color { } impl Color { - const ALL: &[Color] = &[ + const ALL: &'static [Color] = &[ Color::Black, Color::Red, Color::Orange, diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index b0e2c81b..3b69f5e6 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -205,7 +205,8 @@ enum Plan { } impl Plan { - pub const ALL: &[Self] = &[Self::Basic, Self::Pro, Self::Enterprise]; + pub const ALL: &'static [Self] = + &[Self::Basic, Self::Pro, Self::Enterprise]; } impl fmt::Display for Plan { diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 20c3dd42..5b089e8a 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -210,7 +210,7 @@ mod toast { } impl Status { - pub const ALL: &[Self] = + pub const ALL: &'static [Self] = &[Self::Primary, Self::Secondary, Self::Success, Self::Danger]; } diff --git a/highlighter/src/lib.rs b/highlighter/src/lib.rs index 5630756e..63f21fc0 100644 --- a/highlighter/src/lib.rs +++ b/highlighter/src/lib.rs @@ -168,7 +168,7 @@ pub enum Theme { } impl Theme { - pub const ALL: &[Self] = &[ + pub const ALL: &'static [Self] = &[ Self::SolarizedDark, Self::Base16Mocha, Self::Base16Ocean, -- cgit From 781ef1f94c4859aeeb852f801b72be095b8ff82b Mon Sep 17 00:00:00 2001 From: Bingus Date: Thu, 14 Sep 2023 13:58:36 -0700 Subject: Added support for custom shader widget for iced_wgpu backend. --- Cargo.toml | 2 +- core/src/rectangle.rs | 11 + examples/custom_shader/Cargo.toml | 13 + examples/custom_shader/src/camera.rs | 53 ++ examples/custom_shader/src/cubes.rs | 99 ++++ examples/custom_shader/src/main.rs | 174 ++++++ examples/custom_shader/src/pipeline.rs | 600 +++++++++++++++++++++ examples/custom_shader/src/primitive.rs | 95 ++++ examples/custom_shader/src/primitive/buffer.rs | 39 ++ examples/custom_shader/src/primitive/cube.rs | 324 +++++++++++ examples/custom_shader/src/primitive/uniforms.rs | 22 + examples/custom_shader/src/primitive/vertex.rs | 29 + examples/custom_shader/src/shaders/cubes.wgsl | 123 +++++ examples/custom_shader/src/shaders/depth.wgsl | 48 ++ .../src/textures/ice_cube_normal_map.png | Bin 0 -> 1773656 bytes .../custom_shader/src/textures/skybox/neg_x.jpg | Bin 0 -> 7549 bytes .../custom_shader/src/textures/skybox/neg_y.jpg | Bin 0 -> 2722 bytes .../custom_shader/src/textures/skybox/neg_z.jpg | Bin 0 -> 3986 bytes .../custom_shader/src/textures/skybox/pos_x.jpg | Bin 0 -> 5522 bytes .../custom_shader/src/textures/skybox/pos_y.jpg | Bin 0 -> 3382 bytes .../custom_shader/src/textures/skybox/pos_z.jpg | Bin 0 -> 5205 bytes examples/integration/src/main.rs | 1 + graphics/Cargo.toml | 1 - renderer/src/lib.rs | 21 + renderer/src/widget.rs | 3 + renderer/src/widget/shader.rs | 215 ++++++++ renderer/src/widget/shader/event.rs | 21 + renderer/src/widget/shader/program.rs | 60 +++ style/src/theme.rs | 2 +- wgpu/src/backend.rs | 64 ++- wgpu/src/custom.rs | 66 +++ wgpu/src/layer.rs | 16 + wgpu/src/lib.rs | 1 + wgpu/src/primitive.rs | 36 ++ wgpu/src/window/compositor.rs | 2 + widget/Cargo.toml | 1 + widget/src/lib.rs | 3 + 37 files changed, 2139 insertions(+), 6 deletions(-) create mode 100644 examples/custom_shader/Cargo.toml create mode 100644 examples/custom_shader/src/camera.rs create mode 100644 examples/custom_shader/src/cubes.rs create mode 100644 examples/custom_shader/src/main.rs create mode 100644 examples/custom_shader/src/pipeline.rs create mode 100644 examples/custom_shader/src/primitive.rs create mode 100644 examples/custom_shader/src/primitive/buffer.rs create mode 100644 examples/custom_shader/src/primitive/cube.rs create mode 100644 examples/custom_shader/src/primitive/uniforms.rs create mode 100644 examples/custom_shader/src/primitive/vertex.rs create mode 100644 examples/custom_shader/src/shaders/cubes.wgsl create mode 100644 examples/custom_shader/src/shaders/depth.wgsl create mode 100644 examples/custom_shader/src/textures/ice_cube_normal_map.png create mode 100644 examples/custom_shader/src/textures/skybox/neg_x.jpg create mode 100644 examples/custom_shader/src/textures/skybox/neg_y.jpg create mode 100644 examples/custom_shader/src/textures/skybox/neg_z.jpg create mode 100644 examples/custom_shader/src/textures/skybox/pos_x.jpg create mode 100644 examples/custom_shader/src/textures/skybox/pos_y.jpg create mode 100644 examples/custom_shader/src/textures/skybox/pos_z.jpg create mode 100644 renderer/src/widget/shader.rs create mode 100644 renderer/src/widget/shader/event.rs create mode 100644 renderer/src/widget/shader/program.rs create mode 100644 wgpu/src/custom.rs diff --git a/Cargo.toml b/Cargo.toml index d69c95cf..ad4cd1bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ maintenance = { status = "actively-developed" } [features] default = ["wgpu"] # Enable the `wgpu` GPU-accelerated renderer backend -wgpu = ["iced_renderer/wgpu"] +wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"] # Enables the `Image` widget image = ["iced_widget/image", "dep:image"] # Enables the `Svg` widget diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index c1c2eeac..d5437d51 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -183,6 +183,17 @@ impl From> for Rectangle { } } +impl From> for Rectangle { + fn from(rectangle: Rectangle) -> Self { + Rectangle { + x: rectangle.x as u32, + y: rectangle.y as u32, + width: rectangle.width as u32, + height: rectangle.height as u32, + } + } +} + impl std::ops::Add> for Rectangle where T: std::ops::Add, diff --git a/examples/custom_shader/Cargo.toml b/examples/custom_shader/Cargo.toml new file mode 100644 index 00000000..7a927811 --- /dev/null +++ b/examples/custom_shader/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "custom_shader" +version = "0.1.0" +authors = ["Bingus "] +edition = "2021" + +[dependencies] +iced = { path = "../..", features = ["debug", "advanced"]} +image = { version = "0.24.6"} +wgpu = "0.17" +bytemuck = { version = "1.13.1" } +glam = { version = "0.24.0", features = ["bytemuck"] } +rand = "0.8.5" diff --git a/examples/custom_shader/src/camera.rs b/examples/custom_shader/src/camera.rs new file mode 100644 index 00000000..2a49c102 --- /dev/null +++ b/examples/custom_shader/src/camera.rs @@ -0,0 +1,53 @@ +use glam::{mat4, vec3, vec4}; +use iced::Rectangle; + +#[derive(Copy, Clone)] +pub struct Camera { + eye: glam::Vec3, + target: glam::Vec3, + up: glam::Vec3, + fov_y: f32, + near: f32, + far: f32, +} + +impl Default for Camera { + fn default() -> Self { + Self { + eye: vec3(0.0, 2.0, 3.0), + target: glam::Vec3::ZERO, + up: glam::Vec3::Y, + fov_y: 45.0, + near: 0.1, + far: 100.0, + } + } +} + +pub const OPENGL_TO_WGPU_MATRIX: glam::Mat4 = mat4( + vec4(1.0, 0.0, 0.0, 0.0), + vec4(0.0, 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 0.5, 0.0), + vec4(0.0, 0.0, 0.5, 1.0), +); + +impl Camera { + pub fn build_view_proj_matrix(&self, bounds: Rectangle) -> glam::Mat4 { + //TODO looks distorted without padding; base on surface texture size instead? + let aspect_ratio = bounds.width / (bounds.height + 150.0); + + let view = glam::Mat4::look_at_rh(self.eye, self.target, self.up); + let proj = glam::Mat4::perspective_rh( + self.fov_y, + aspect_ratio, + self.near, + self.far, + ); + + OPENGL_TO_WGPU_MATRIX * proj * view + } + + pub fn position(&self) -> glam::Vec4 { + glam::Vec4::from((self.eye, 0.0)) + } +} diff --git a/examples/custom_shader/src/cubes.rs b/examples/custom_shader/src/cubes.rs new file mode 100644 index 00000000..8dbba4b1 --- /dev/null +++ b/examples/custom_shader/src/cubes.rs @@ -0,0 +1,99 @@ +use crate::camera::Camera; +use crate::primitive; +use crate::primitive::cube::Cube; +use glam::Vec3; +use iced::widget::shader; +use iced::{mouse, Color, Rectangle}; +use rand::Rng; +use std::cmp::Ordering; +use std::iter; +use std::time::Duration; + +pub const MAX: u32 = 500; + +#[derive(Clone)] +pub struct Cubes { + pub size: f32, + pub cubes: Vec, + pub camera: Camera, + pub show_depth_buffer: bool, + pub light_color: Color, +} + +impl Cubes { + pub fn new() -> Self { + let mut cubes = Self { + size: 0.2, + cubes: vec![], + camera: Camera::default(), + show_depth_buffer: false, + light_color: Color::WHITE, + }; + + cubes.adjust_num_cubes(MAX); + + cubes + } + + pub fn update(&mut self, time: Duration) { + for cube in self.cubes.iter_mut() { + cube.update(self.size, time.as_secs_f32()); + } + } + + pub fn adjust_num_cubes(&mut self, num_cubes: u32) { + let curr_cubes = self.cubes.len() as u32; + + match num_cubes.cmp(&curr_cubes) { + Ordering::Greater => { + // spawn + let cubes_2_spawn = (num_cubes - curr_cubes) as usize; + + let mut cubes = 0; + self.cubes.extend(iter::from_fn(|| { + if cubes < cubes_2_spawn { + cubes += 1; + Some(Cube::new(self.size, rnd_origin())) + } else { + None + } + })); + } + Ordering::Less => { + // chop + let cubes_2_cut = curr_cubes - num_cubes; + let new_len = self.cubes.len() - cubes_2_cut as usize; + self.cubes.truncate(new_len); + } + _ => {} + } + } +} + +impl shader::Program for Cubes { + type State = (); + type Primitive = primitive::Primitive; + + fn draw( + &self, + _state: &Self::State, + _cursor: mouse::Cursor, + bounds: Rectangle, + ) -> Self::Primitive { + primitive::Primitive::new( + &self.cubes, + &self.camera, + bounds, + self.show_depth_buffer, + self.light_color, + ) + } +} + +fn rnd_origin() -> Vec3 { + Vec3::new( + rand::thread_rng().gen_range(-4.0..4.0), + rand::thread_rng().gen_range(-4.0..4.0), + rand::thread_rng().gen_range(-4.0..2.0), + ) +} diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs new file mode 100644 index 00000000..76fa1625 --- /dev/null +++ b/examples/custom_shader/src/main.rs @@ -0,0 +1,174 @@ +mod camera; +mod cubes; +mod pipeline; +mod primitive; + +use crate::cubes::Cubes; +use iced::widget::{ + checkbox, column, container, row, slider, text, vertical_space, Shader, +}; +use iced::{ + executor, window, Alignment, Application, Color, Command, Element, Length, + Renderer, Subscription, Theme, +}; +use std::time::Instant; + +fn main() -> iced::Result { + IcedCubes::run(iced::Settings::default()) +} + +struct IcedCubes { + start: Instant, + cubes: Cubes, + num_cubes_slider: u32, +} + +impl Default for IcedCubes { + fn default() -> Self { + Self { + start: Instant::now(), + cubes: Cubes::new(), + num_cubes_slider: cubes::MAX, + } + } +} + +#[derive(Debug, Clone)] +enum Message { + CubeAmountChanged(u32), + CubeSizeChanged(f32), + Tick(Instant), + ShowDepthBuffer(bool), + LightColorChanged(Color), +} + +impl Application for IcedCubes { + type Executor = executor::Default; + type Message = Message; + type Theme = Theme; + type Flags = (); + + fn new(_flags: Self::Flags) -> (Self, Command) { + (IcedCubes::default(), Command::none()) + } + + fn title(&self) -> String { + "Iced Cubes".to_string() + } + + fn update(&mut self, message: Self::Message) -> Command { + match message { + Message::CubeAmountChanged(num) => { + self.num_cubes_slider = num; + self.cubes.adjust_num_cubes(num); + } + Message::CubeSizeChanged(size) => { + self.cubes.size = size; + } + Message::Tick(time) => { + self.cubes.update(time - self.start); + } + Message::ShowDepthBuffer(show) => { + self.cubes.show_depth_buffer = show; + } + Message::LightColorChanged(color) => { + self.cubes.light_color = color; + } + } + + Command::none() + } + + fn view(&self) -> Element<'_, Self::Message, Renderer> { + let top_controls = row![ + control( + "Amount", + slider( + 1..=cubes::MAX, + self.num_cubes_slider, + Message::CubeAmountChanged + ) + .width(100) + ), + control( + "Size", + slider(0.1..=0.25, self.cubes.size, Message::CubeSizeChanged) + .step(0.01) + .width(100), + ), + checkbox( + "Show Depth Buffer", + self.cubes.show_depth_buffer, + Message::ShowDepthBuffer + ), + ] + .spacing(40); + + let bottom_controls = row![ + control( + "R", + slider(0.0..=1.0, self.cubes.light_color.r, move |r| { + Message::LightColorChanged(Color { + r, + ..self.cubes.light_color + }) + }) + .step(0.01) + .width(100) + ), + control( + "G", + slider(0.0..=1.0, self.cubes.light_color.g, move |g| { + Message::LightColorChanged(Color { + g, + ..self.cubes.light_color + }) + }) + .step(0.01) + .width(100) + ), + control( + "B", + slider(0.0..=1.0, self.cubes.light_color.b, move |b| { + Message::LightColorChanged(Color { + b, + ..self.cubes.light_color + }) + }) + .step(0.01) + .width(100) + ) + ] + .spacing(40); + + let controls = column![top_controls, bottom_controls,] + .spacing(10) + .align_items(Alignment::Center); + + let shader = Shader::new(&self.cubes) + .width(Length::Fill) + .height(Length::Fill); + + container( + column![shader, controls, vertical_space(20),] + .spacing(40) + .align_items(Alignment::Center), + ) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } + + fn subscription(&self) -> Subscription { + window::frames().map(Message::Tick) + } +} + +fn control<'a>( + label: &'static str, + control: impl Into>, +) -> Element<'a, Message> { + row![text(label), control.into()].spacing(10).into() +} diff --git a/examples/custom_shader/src/pipeline.rs b/examples/custom_shader/src/pipeline.rs new file mode 100644 index 00000000..9dd154e8 --- /dev/null +++ b/examples/custom_shader/src/pipeline.rs @@ -0,0 +1,600 @@ +use crate::primitive; +use crate::primitive::cube; +use crate::primitive::{Buffer, Uniforms}; +use iced::{Rectangle, Size}; +use wgpu::util::DeviceExt; + +const SKY_TEXTURE_SIZE: u32 = 128; + +pub struct Pipeline { + pipeline: wgpu::RenderPipeline, + vertices: wgpu::Buffer, + cubes: Buffer, + uniforms: wgpu::Buffer, + uniform_bind_group: wgpu::BindGroup, + depth_texture_size: Size, + depth_view: wgpu::TextureView, + depth_pipeline: DepthPipeline, +} + +impl Pipeline { + pub fn new( + device: &wgpu::Device, + queue: &wgpu::Queue, + format: wgpu::TextureFormat, + target_size: Size, + ) -> Self { + //vertices of one cube + let vertices = + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("cubes vertex buffer"), + contents: bytemuck::cast_slice(&cube::Raw::vertices()), + usage: wgpu::BufferUsages::VERTEX, + }); + + //cube instance data + let cubes_buffer = Buffer::new( + device, + "cubes instance buffer", + std::mem::size_of::() as u64, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + //uniforms for all cubes + let uniforms = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("cubes uniform buffer"), + size: std::mem::size_of::() as u64, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + //depth buffer + let depth_texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("cubes depth texture"), + size: wgpu::Extent3d { + width: target_size.width, + height: target_size.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth32Float, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }); + + let depth_view = + depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let normal_map_data = load_normal_map_data(); + + //normal map + let normal_texture = device.create_texture_with_data( + queue, + &wgpu::TextureDescriptor { + label: Some("cubes normal map texture"), + size: wgpu::Extent3d { + width: 1024, + height: 1024, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + &normal_map_data, + ); + + let normal_view = + normal_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + //skybox texture for reflection/refraction + let skybox_data = load_skybox_data(); + + let skybox_texture = device.create_texture_with_data( + queue, + &wgpu::TextureDescriptor { + label: Some("cubes skybox texture"), + size: wgpu::Extent3d { + width: SKY_TEXTURE_SIZE, + height: SKY_TEXTURE_SIZE, + depth_or_array_layers: 6, //one for each face of the cube + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + &skybox_data, + ); + + let sky_view = + skybox_texture.create_view(&wgpu::TextureViewDescriptor { + label: Some("cubes skybox texture view"), + dimension: Some(wgpu::TextureViewDimension::Cube), + ..Default::default() + }); + + let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("cubes skybox sampler"), + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + + let uniform_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("cubes uniform bind group layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { + filterable: true, + }, + view_dimension: wgpu::TextureViewDimension::Cube, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler( + wgpu::SamplerBindingType::Filtering, + ), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { + filterable: true, + }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + ], + }); + + let uniform_bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("cubes uniform bind group"), + layout: &uniform_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: uniforms.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&sky_view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Sampler(&sky_sampler), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::TextureView( + &normal_view, + ), + }, + ], + }); + + let layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("cubes pipeline layout"), + bind_group_layouts: &[&uniform_bind_group_layout], + push_constant_ranges: &[], + }); + + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("cubes shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + include_str!("shaders/cubes.wgsl"), + )), + }); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("cubes pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[primitive::Vertex::desc(), cube::Raw::desc()], + }, + primitive: wgpu::PrimitiveState::default(), + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::Max, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: None, + }); + + let depth_pipeline = DepthPipeline::new( + device, + format, + depth_texture.create_view(&wgpu::TextureViewDescriptor::default()), + ); + + Self { + pipeline, + cubes: cubes_buffer, + uniforms, + uniform_bind_group, + vertices, + depth_texture_size: target_size, + depth_view, + depth_pipeline, + } + } + + fn update_depth_texture(&mut self, device: &wgpu::Device, size: Size) { + if self.depth_texture_size.height != size.height + || self.depth_texture_size.width != size.width + { + let text = device.create_texture(&wgpu::TextureDescriptor { + label: Some("cubes depth texture"), + size: wgpu::Extent3d { + width: size.width, + height: size.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth32Float, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }); + + self.depth_view = + text.create_view(&wgpu::TextureViewDescriptor::default()); + self.depth_texture_size = size; + + self.depth_pipeline.update(device, &text); + } + } + + pub fn update( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + target_size: Size, + uniforms: &Uniforms, + num_cubes: usize, + cubes: &[cube::Raw], + ) { + //recreate depth texture if surface texture size has changed + self.update_depth_texture(device, target_size); + + // update uniforms + queue.write_buffer(&self.uniforms, 0, bytemuck::bytes_of(uniforms)); + + //resize cubes vertex buffer if cubes amount changed + let new_size = num_cubes * std::mem::size_of::(); + self.cubes.resize(device, new_size as u64); + + //always write new cube data since they are constantly rotating + queue.write_buffer(&self.cubes.raw, 0, bytemuck::cast_slice(cubes)); + } + + pub fn render( + &self, + target: &wgpu::TextureView, + encoder: &mut wgpu::CommandEncoder, + bounds: Rectangle, + num_cubes: u32, + show_depth: bool, + ) { + { + let mut pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("cubes.pipeline.pass"), + color_attachments: &[Some( + wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + }, + )], + depth_stencil_attachment: Some( + wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }, + ), + }); + + pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &self.uniform_bind_group, &[]); + pass.set_vertex_buffer(0, self.vertices.slice(..)); + pass.set_vertex_buffer(1, self.cubes.raw.slice(..)); + pass.draw(0..36, 0..num_cubes); + } + + if show_depth { + self.depth_pipeline.render(encoder, target, bounds); + } + } +} + +struct DepthPipeline { + pipeline: wgpu::RenderPipeline, + bind_group_layout: wgpu::BindGroupLayout, + bind_group: wgpu::BindGroup, + sampler: wgpu::Sampler, + depth_view: wgpu::TextureView, +} + +impl DepthPipeline { + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + depth_texture: wgpu::TextureView, + ) -> Self { + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("cubes.depth_pipeline.sampler"), + ..Default::default() + }); + + let bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("cubes.depth_pipeline.bind_group_layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler( + wgpu::SamplerBindingType::NonFiltering, + ), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { + filterable: false, + }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + ], + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("cubes.depth_pipeline.bind_group"), + layout: &bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView( + &depth_texture, + ), + }, + ], + }); + + let layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("cubes.depth_pipeline.layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("cubes.depth_pipeline.shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + include_str!("shaders/depth.wgsl"), + )), + }); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("cubes.depth_pipeline.pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + }, + primitive: Default::default(), + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: false, + depth_compare: wgpu::CompareFunction::Less, + stencil: Default::default(), + bias: Default::default(), + }), + multisample: Default::default(), + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: None, + }); + + Self { + pipeline, + bind_group_layout, + bind_group, + sampler, + depth_view: depth_texture, + } + } + + pub fn update( + &mut self, + device: &wgpu::Device, + depth_texture: &wgpu::Texture, + ) { + self.depth_view = + depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + self.bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("cubes.depth_pipeline.bind_group"), + layout: &self.bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Sampler(&self.sampler), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView( + &self.depth_view, + ), + }, + ], + }); + } + + pub fn render( + &self, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + bounds: Rectangle, + ) { + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("cubes.pipeline.depth_pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + })], + depth_stencil_attachment: Some( + wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_view, + depth_ops: None, + stencil_ops: None, + }, + ), + }); + + pass.set_scissor_rect(bounds.x, bounds.y, bounds.width, bounds.height); + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &self.bind_group, &[]); + pass.draw(0..6, 0..1); + } +} + +fn load_skybox_data() -> Vec { + let pos_x: &[u8] = include_bytes!("textures/skybox/pos_x.jpg"); + let neg_x: &[u8] = include_bytes!("textures/skybox/neg_x.jpg"); + let pos_y: &[u8] = include_bytes!("textures/skybox/pos_y.jpg"); + let neg_y: &[u8] = include_bytes!("textures/skybox/neg_y.jpg"); + let pos_z: &[u8] = include_bytes!("textures/skybox/pos_z.jpg"); + let neg_z: &[u8] = include_bytes!("textures/skybox/neg_z.jpg"); + + let data: [&[u8]; 6] = [pos_x, neg_x, pos_y, neg_y, pos_z, neg_z]; + + data.iter().fold(vec![], |mut acc, bytes| { + let i = image::load_from_memory_with_format( + bytes, + image::ImageFormat::Jpeg, + ) + .unwrap() + .to_rgba8() + .into_raw(); + + acc.extend(i); + acc + }) +} + +fn load_normal_map_data() -> Vec { + let bytes: &[u8] = include_bytes!("textures/ice_cube_normal_map.png"); + + image::load_from_memory_with_format(bytes, image::ImageFormat::Png) + .unwrap() + .to_rgba8() + .into_raw() +} diff --git a/examples/custom_shader/src/primitive.rs b/examples/custom_shader/src/primitive.rs new file mode 100644 index 00000000..2201218f --- /dev/null +++ b/examples/custom_shader/src/primitive.rs @@ -0,0 +1,95 @@ +pub mod cube; +pub mod vertex; + +mod buffer; +mod uniforms; + +use crate::camera::Camera; +use crate::pipeline::Pipeline; +use crate::primitive::cube::Cube; +use iced::advanced::graphics::Transformation; +use iced::widget::shader; +use iced::{Color, Rectangle, Size}; + +pub use crate::primitive::vertex::Vertex; +pub use buffer::Buffer; +pub use uniforms::Uniforms; + +/// A collection of `Cube`s that can be rendered. +#[derive(Debug)] +pub struct Primitive { + cubes: Vec, + uniforms: Uniforms, + show_depth_buffer: bool, +} + +impl Primitive { + pub fn new( + cubes: &[Cube], + camera: &Camera, + bounds: Rectangle, + show_depth_buffer: bool, + light_color: Color, + ) -> Self { + let uniforms = Uniforms::new(camera, bounds, light_color); + + Self { + cubes: cubes + .iter() + .map(cube::Raw::from_cube) + .collect::>(), + uniforms, + show_depth_buffer, + } + } +} + +impl shader::Primitive for Primitive { + fn prepare( + &self, + format: wgpu::TextureFormat, + device: &wgpu::Device, + queue: &wgpu::Queue, + target_size: Size, + _scale_factor: f32, + _transform: Transformation, + storage: &mut shader::Storage, + ) { + if !storage.has::() { + storage.store(Pipeline::new(device, queue, format, target_size)) + } + + let pipeline = storage.get_mut::().unwrap(); + + //upload data to GPU + pipeline.update( + device, + queue, + target_size, + &self.uniforms, + self.cubes.len(), + &self.cubes, + ); + } + + fn render( + &self, + storage: &shader::Storage, + bounds: Rectangle, + target: &wgpu::TextureView, + _target_size: Size, + encoder: &mut wgpu::CommandEncoder, + ) { + //at this point our pipeline should always be initialized + let pipeline = storage.get::().unwrap(); + + //render primitive + pipeline.render( + target, + encoder, + bounds, + self.cubes.len() as u32, + self.show_depth_buffer, + ) + } +} diff --git a/examples/custom_shader/src/primitive/buffer.rs b/examples/custom_shader/src/primitive/buffer.rs new file mode 100644 index 00000000..377ce1bb --- /dev/null +++ b/examples/custom_shader/src/primitive/buffer.rs @@ -0,0 +1,39 @@ +// A custom buffer container for dynamic resizing. +pub struct Buffer { + pub raw: wgpu::Buffer, + label: &'static str, + size: u64, + usage: wgpu::BufferUsages, +} + +impl Buffer { + pub fn new( + device: &wgpu::Device, + label: &'static str, + size: u64, + usage: wgpu::BufferUsages, + ) -> Self { + Self { + raw: device.create_buffer(&wgpu::BufferDescriptor { + label: Some(label), + size, + usage, + mapped_at_creation: false, + }), + label, + size, + usage, + } + } + + pub fn resize(&mut self, device: &wgpu::Device, new_size: u64) { + if new_size > self.size { + self.raw = device.create_buffer(&wgpu::BufferDescriptor { + label: Some(self.label), + size: new_size, + usage: self.usage, + mapped_at_creation: false, + }); + } + } +} diff --git a/examples/custom_shader/src/primitive/cube.rs b/examples/custom_shader/src/primitive/cube.rs new file mode 100644 index 00000000..c23f2132 --- /dev/null +++ b/examples/custom_shader/src/primitive/cube.rs @@ -0,0 +1,324 @@ +use crate::primitive::Vertex; +use glam::{vec2, vec3, Vec3}; +use rand::{thread_rng, Rng}; + +/// A single instance of a cube. +#[derive(Debug, Clone)] +pub struct Cube { + pub rotation: glam::Quat, + pub position: Vec3, + pub size: f32, + rotation_dir: f32, + rotation_axis: glam::Vec3, +} + +impl Default for Cube { + fn default() -> Self { + Self { + rotation: glam::Quat::IDENTITY, + position: glam::Vec3::ZERO, + size: 0.1, + rotation_dir: 1.0, + rotation_axis: glam::Vec3::Y, + } + } +} + +impl Cube { + pub fn new(size: f32, origin: Vec3) -> Self { + let rnd = thread_rng().gen_range(0.0..=1.0f32); + + Self { + rotation: glam::Quat::IDENTITY, + position: origin + Vec3::new(0.1, 0.1, 0.1), + size, + rotation_dir: if rnd <= 0.5 { -1.0 } else { 1.0 }, + rotation_axis: if rnd <= 0.33 { + glam::Vec3::Y + } else if rnd <= 0.66 { + glam::Vec3::X + } else { + glam::Vec3::Z + }, + } + } + + pub fn update(&mut self, size: f32, time: f32) { + self.rotation = glam::Quat::from_axis_angle( + self.rotation_axis, + time / 2.0 * self.rotation_dir, + ); + self.size = size; + } +} + +#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Debug)] +#[repr(C)] +pub struct Raw { + transformation: glam::Mat4, + normal: glam::Mat3, + _padding: [f32; 3], +} + +impl Raw { + const ATTRIBS: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![ + //cube transformation matrix + 4 => Float32x4, + 5 => Float32x4, + 6 => Float32x4, + 7 => Float32x4, + //normal rotation matrix + 8 => Float32x3, + 9 => Float32x3, + 10 => Float32x3, + ]; + + pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &Self::ATTRIBS, + } + } +} + +impl Raw { + pub fn from_cube(cube: &Cube) -> Raw { + Raw { + transformation: glam::Mat4::from_scale_rotation_translation( + glam::vec3(cube.size, cube.size, cube.size), + cube.rotation, + cube.position, + ), + normal: glam::Mat3::from_quat(cube.rotation), + _padding: [0.0; 3], + } + } + + pub fn vertices() -> [Vertex; 36] { + [ + //face 1 + Vertex { + pos: vec3(-0.5, -0.5, -0.5), + normal: vec3(0.0, 0.0, -1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + Vertex { + pos: vec3(0.5, -0.5, -0.5), + normal: vec3(0.0, 0.0, -1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 1.0), + }, + Vertex { + pos: vec3(0.5, 0.5, -0.5), + normal: vec3(0.0, 0.0, -1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(0.5, 0.5, -0.5), + normal: vec3(0.0, 0.0, -1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, 0.5, -0.5), + normal: vec3(0.0, 0.0, -1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, -0.5, -0.5), + normal: vec3(0.0, 0.0, -1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + //face 2 + Vertex { + pos: vec3(-0.5, -0.5, 0.5), + normal: vec3(0.0, 0.0, 1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + Vertex { + pos: vec3(0.5, -0.5, 0.5), + normal: vec3(0.0, 0.0, 1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 1.0), + }, + Vertex { + pos: vec3(0.5, 0.5, 0.5), + normal: vec3(0.0, 0.0, 1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(0.5, 0.5, 0.5), + normal: vec3(0.0, 0.0, 1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, 0.5, 0.5), + normal: vec3(0.0, 0.0, 1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, -0.5, 0.5), + normal: vec3(0.0, 0.0, 1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + //face 3 + Vertex { + pos: vec3(-0.5, 0.5, 0.5), + normal: vec3(-1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(0.0, 1.0), + }, + Vertex { + pos: vec3(-0.5, 0.5, -0.5), + normal: vec3(-1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(1.0, 1.0), + }, + Vertex { + pos: vec3(-0.5, -0.5, -0.5), + normal: vec3(-1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, -0.5, -0.5), + normal: vec3(-1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, -0.5, 0.5), + normal: vec3(-1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(0.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, 0.5, 0.5), + normal: vec3(-1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(0.0, 1.0), + }, + //face 4 + Vertex { + pos: vec3(0.5, 0.5, 0.5), + normal: vec3(1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(0.0, 1.0), + }, + Vertex { + pos: vec3(0.5, 0.5, -0.5), + normal: vec3(1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(1.0, 1.0), + }, + Vertex { + pos: vec3(0.5, -0.5, -0.5), + normal: vec3(1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(0.5, -0.5, -0.5), + normal: vec3(1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(0.5, -0.5, 0.5), + normal: vec3(1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(0.0, 0.0), + }, + Vertex { + pos: vec3(0.5, 0.5, 0.5), + normal: vec3(1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(0.0, 1.0), + }, + //face 5 + Vertex { + pos: vec3(-0.5, -0.5, -0.5), + normal: vec3(0.0, -1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + Vertex { + pos: vec3(0.5, -0.5, -0.5), + normal: vec3(0.0, -1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 1.0), + }, + Vertex { + pos: vec3(0.5, -0.5, 0.5), + normal: vec3(0.0, -1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(0.5, -0.5, 0.5), + normal: vec3(0.0, -1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, -0.5, 0.5), + normal: vec3(0.0, -1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, -0.5, -0.5), + normal: vec3(0.0, -1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + //face 6 + Vertex { + pos: vec3(-0.5, 0.5, -0.5), + normal: vec3(0.0, 1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + Vertex { + pos: vec3(0.5, 0.5, -0.5), + normal: vec3(0.0, 1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 1.0), + }, + Vertex { + pos: vec3(0.5, 0.5, 0.5), + normal: vec3(0.0, 1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(0.5, 0.5, 0.5), + normal: vec3(0.0, 1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, 0.5, 0.5), + normal: vec3(0.0, 1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, 0.5, -0.5), + normal: vec3(0.0, 1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + ] + } +} diff --git a/examples/custom_shader/src/primitive/uniforms.rs b/examples/custom_shader/src/primitive/uniforms.rs new file mode 100644 index 00000000..4fcb413b --- /dev/null +++ b/examples/custom_shader/src/primitive/uniforms.rs @@ -0,0 +1,22 @@ +use crate::camera::Camera; +use iced::{Color, Rectangle}; + +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct Uniforms { + camera_proj: glam::Mat4, + camera_pos: glam::Vec4, + light_color: glam::Vec4, +} + +impl Uniforms { + pub fn new(camera: &Camera, bounds: Rectangle, light_color: Color) -> Self { + let camera_proj = camera.build_view_proj_matrix(bounds); + + Self { + camera_proj, + camera_pos: camera.position(), + light_color: glam::Vec4::from(light_color.into_linear()), + } + } +} diff --git a/examples/custom_shader/src/primitive/vertex.rs b/examples/custom_shader/src/primitive/vertex.rs new file mode 100644 index 00000000..6d17aa0f --- /dev/null +++ b/examples/custom_shader/src/primitive/vertex.rs @@ -0,0 +1,29 @@ +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct Vertex { + pub pos: glam::Vec3, + pub normal: glam::Vec3, + pub tangent: glam::Vec3, + pub uv: glam::Vec2, +} + +impl Vertex { + const ATTRIBS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![ + //position + 0 => Float32x3, + //normal + 1 => Float32x3, + //tangent + 2 => Float32x3, + //uv + 3 => Float32x2, + ]; + + pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &Self::ATTRIBS, + } + } +} diff --git a/examples/custom_shader/src/shaders/cubes.wgsl b/examples/custom_shader/src/shaders/cubes.wgsl new file mode 100644 index 00000000..cd7f94d8 --- /dev/null +++ b/examples/custom_shader/src/shaders/cubes.wgsl @@ -0,0 +1,123 @@ +struct Uniforms { + projection: mat4x4, + camera_pos: vec4, + light_color: vec4, +} + +const LIGHT_POS: vec3 = vec3(0.0, 3.0, 3.0); + +@group(0) @binding(0) var uniforms: Uniforms; +@group(0) @binding(1) var sky_texture: texture_cube; +@group(0) @binding(2) var tex_sampler: sampler; +@group(0) @binding(3) var normal_texture: texture_2d; + +struct Vertex { + @location(0) position: vec3, + @location(1) normal: vec3, + @location(2) tangent: vec3, + @location(3) uv: vec2, +} + +struct Cube { + @location(4) matrix_0: vec4, + @location(5) matrix_1: vec4, + @location(6) matrix_2: vec4, + @location(7) matrix_3: vec4, + @location(8) normal_matrix_0: vec3, + @location(9) normal_matrix_1: vec3, + @location(10) normal_matrix_2: vec3, +} + +struct Output { + @builtin(position) clip_pos: vec4, + @location(0) uv: vec2, + @location(1) tangent_pos: vec3, + @location(2) tangent_camera_pos: vec3, + @location(3) tangent_light_pos: vec3, +} + +@vertex +fn vs_main(vertex: Vertex, cube: Cube) -> Output { + let cube_matrix = mat4x4( + cube.matrix_0, + cube.matrix_1, + cube.matrix_2, + cube.matrix_3, + ); + + let normal_matrix = mat3x3( + cube.normal_matrix_0, + cube.normal_matrix_1, + cube.normal_matrix_2, + ); + + //convert to tangent space to calculate lighting in same coordinate space as normal map sample + let tangent = normalize(normal_matrix * vertex.tangent); + let normal = normalize(normal_matrix * vertex.normal); + let bitangent = cross(tangent, normal); + + //shift everything into tangent space + let tbn = transpose(mat3x3(tangent, bitangent, normal)); + + let world_pos = cube_matrix * vec4(vertex.position, 1.0); + + var out: Output; + out.clip_pos = uniforms.projection * world_pos; + out.uv = vertex.uv; + out.tangent_pos = tbn * world_pos.xyz; + out.tangent_camera_pos = tbn * uniforms.camera_pos.xyz; + out.tangent_light_pos = tbn * LIGHT_POS; + + return out; +} + +//cube properties +const CUBE_BASE_COLOR: vec4 = vec4(0.294118, 0.462745, 0.611765, 0.6); +const SHINE_DAMPER: f32 = 1.0; +const REFLECTIVITY: f32 = 0.8; +const REFRACTION_INDEX: f32 = 1.31; + +//fog, for the ~* cinematic effect *~ +const FOG_DENSITY: f32 = 0.15; +const FOG_GRADIENT: f32 = 8.0; +const FOG_COLOR: vec4 = vec4(1.0, 1.0, 1.0, 1.0); + +@fragment +fn fs_main(in: Output) -> @location(0) vec4 { + let to_camera = in.tangent_camera_pos - in.tangent_pos; + + //normal sample from texture + var normal = textureSample(normal_texture, tex_sampler, in.uv).xyz; + normal = normal * 2.0 - 1.0; + + //diffuse + let dir_to_light: vec3 = normalize(in.tangent_light_pos - in.tangent_pos); + let brightness = max(dot(normal, dir_to_light), 0.0); + let diffuse: vec3 = brightness * uniforms.light_color.xyz; + + //specular + let dir_to_camera = normalize(to_camera); + let light_dir = -dir_to_light; + let reflected_light_dir = reflect(light_dir, normal); + let specular_factor = max(dot(reflected_light_dir, dir_to_camera), 0.0); + let damped_factor = pow(specular_factor, SHINE_DAMPER); + let specular: vec3 = damped_factor * uniforms.light_color.xyz * REFLECTIVITY; + + //fog + let distance = length(to_camera); + let visibility = clamp(exp(-pow((distance * FOG_DENSITY), FOG_GRADIENT)), 0.0, 1.0); + + //reflection + let reflection_dir = reflect(dir_to_camera, normal); + let reflection_color = textureSample(sky_texture, tex_sampler, reflection_dir); + let refraction_dir = refract(dir_to_camera, normal, REFRACTION_INDEX); + let refraction_color = textureSample(sky_texture, tex_sampler, refraction_dir); + let final_reflect_color = mix(reflection_color, refraction_color, 0.5); + + //mix it all together! + var color = vec4(CUBE_BASE_COLOR.xyz * diffuse + specular, CUBE_BASE_COLOR.w); + color = mix(color, final_reflect_color, 0.8); + color = mix(FOG_COLOR, color, visibility); + + return color; +} diff --git a/examples/custom_shader/src/shaders/depth.wgsl b/examples/custom_shader/src/shaders/depth.wgsl new file mode 100644 index 00000000..a3f7e5ec --- /dev/null +++ b/examples/custom_shader/src/shaders/depth.wgsl @@ -0,0 +1,48 @@ +var positions: array, 6> = array, 6>( + vec2(-1.0, 1.0), + vec2(-1.0, -1.0), + vec2(1.0, -1.0), + vec2(-1.0, 1.0), + vec2(1.0, 1.0), + vec2(1.0, -1.0) +); + +var uvs: array, 6> = array, 6>( + vec2(0.0, 0.0), + vec2(0.0, 1.0), + vec2(1.0, 1.0), + vec2(0.0, 0.0), + vec2(1.0, 0.0), + vec2(1.0, 1.0) +); + +@group(0) @binding(0) var depth_sampler: sampler; +@group(0) @binding(1) var depth_texture: texture_2d; + +struct Output { + @builtin(position) position: vec4, + @location(0) uv: vec2, +} + +@vertex +fn vs_main(@builtin(vertex_index) v_index: u32) -> Output { + var out: Output; + + out.position = vec4(positions[v_index], 0.0, 1.0); + out.uv = uvs[v_index]; + + return out; +} + +@fragment +fn fs_main(input: Output) -> @location(0) vec4 { + let depth = textureSample(depth_texture, depth_sampler, input.uv).r; + + if (depth > .9999) { + discard; + } + + let c = 1.0 - depth; + + return vec4(c, c, c, 1.0); +} diff --git a/examples/custom_shader/src/textures/ice_cube_normal_map.png b/examples/custom_shader/src/textures/ice_cube_normal_map.png new file mode 100644 index 00000000..7b4b7228 Binary files /dev/null and b/examples/custom_shader/src/textures/ice_cube_normal_map.png differ diff --git a/examples/custom_shader/src/textures/skybox/neg_x.jpg b/examples/custom_shader/src/textures/skybox/neg_x.jpg new file mode 100644 index 00000000..00cc783d Binary files /dev/null and b/examples/custom_shader/src/textures/skybox/neg_x.jpg differ diff --git a/examples/custom_shader/src/textures/skybox/neg_y.jpg b/examples/custom_shader/src/textures/skybox/neg_y.jpg new file mode 100644 index 00000000..548f6445 Binary files /dev/null and b/examples/custom_shader/src/textures/skybox/neg_y.jpg differ diff --git a/examples/custom_shader/src/textures/skybox/neg_z.jpg b/examples/custom_shader/src/textures/skybox/neg_z.jpg new file mode 100644 index 00000000..5698512e Binary files /dev/null and b/examples/custom_shader/src/textures/skybox/neg_z.jpg differ diff --git a/examples/custom_shader/src/textures/skybox/pos_x.jpg b/examples/custom_shader/src/textures/skybox/pos_x.jpg new file mode 100644 index 00000000..dddecba7 Binary files /dev/null and b/examples/custom_shader/src/textures/skybox/pos_x.jpg differ diff --git a/examples/custom_shader/src/textures/skybox/pos_y.jpg b/examples/custom_shader/src/textures/skybox/pos_y.jpg new file mode 100644 index 00000000..361427fd Binary files /dev/null and b/examples/custom_shader/src/textures/skybox/pos_y.jpg differ diff --git a/examples/custom_shader/src/textures/skybox/pos_z.jpg b/examples/custom_shader/src/textures/skybox/pos_z.jpg new file mode 100644 index 00000000..0085a49e Binary files /dev/null and b/examples/custom_shader/src/textures/skybox/pos_z.jpg differ diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index c26d52fe..0f32fca0 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -271,6 +271,7 @@ pub fn main() -> Result<(), Box> { &queue, &mut encoder, None, + frame.texture.format(), &view, primitive, &viewport, diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index a7aea352..6741d7cf 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -16,7 +16,6 @@ all-features = true [features] geometry = ["lyon_path"] -opengl = [] image = ["dep:image", "kamadak-exif"] web-colors = [] diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 78dec847..8c5ee2f0 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -7,6 +7,7 @@ pub mod compositor; pub mod geometry; mod settings; +pub mod widget; pub use iced_graphics as graphics; pub use iced_graphics::core; @@ -59,6 +60,26 @@ impl Renderer { } } } + + pub fn draw_custom( + &mut self, + bounds: Rectangle, + primitive: P, + ) { + match self { + Renderer::TinySkia(_) => { + log::warn!( + "Custom shader primitive is unavailable with tiny-skia." + ); + } + #[cfg(feature = "wgpu")] + Renderer::Wgpu(renderer) => { + renderer.draw_primitive(iced_wgpu::Primitive::Custom( + iced_wgpu::primitive::Custom::shader(bounds, primitive), + )) + } + } + } } impl core::Renderer for Renderer { diff --git a/renderer/src/widget.rs b/renderer/src/widget.rs index 6c0c2a83..0422c99c 100644 --- a/renderer/src/widget.rs +++ b/renderer/src/widget.rs @@ -9,3 +9,6 @@ pub mod qr_code; #[cfg(feature = "qr_code")] pub use qr_code::QRCode; + +#[cfg(feature = "wgpu")] +pub mod shader; diff --git a/renderer/src/widget/shader.rs b/renderer/src/widget/shader.rs new file mode 100644 index 00000000..da42a7dd --- /dev/null +++ b/renderer/src/widget/shader.rs @@ -0,0 +1,215 @@ +//! A custom shader widget for wgpu applications. +use crate::core::event::Status; +use crate::core::layout::{Limits, Node}; +use crate::core::mouse::{Cursor, Interaction}; +use crate::core::renderer::Style; +use crate::core::widget::tree::{State, Tag}; +use crate::core::widget::{tree, Tree}; +use crate::core::{ + self, layout, mouse, widget, Clipboard, Element, Layout, Length, Rectangle, + Shell, Size, Widget, +}; +use std::marker::PhantomData; + +mod event; +mod program; + +pub use event::Event; +pub use iced_wgpu::custom::Primitive; +pub use iced_wgpu::custom::Storage; +pub use program::Program; + +/// A widget which can render custom shaders with Iced's `wgpu` backend. +/// +/// Must be initialized with a [`Program`], which describes the internal widget state & how +/// its [`Program::Primitive`]s are drawn. +#[allow(missing_debug_implementations)] +pub struct Shader> { + width: Length, + height: Length, + program: P, + _message: PhantomData, +} + +impl> Shader { + /// Create a new custom [`Shader`]. + pub fn new(program: P) -> Self { + Self { + width: Length::Fixed(100.0), + height: Length::Fixed(100.0), + program, + _message: PhantomData, + } + } + + /// Set the `width` of the custom [`Shader`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Set the `height` of the custom [`Shader`]. + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } +} + +impl Widget> + for Shader +where + P: Program, +{ + fn tag(&self) -> Tag { + struct Tag(T); + tree::Tag::of::>() + } + + fn state(&self) -> State { + tree::State::new(P::State::default()) + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + _tree: &mut Tree, + _renderer: &crate::Renderer, + limits: &Limits, + ) -> Node { + let limits = limits.width(self.width).height(self.height); + let size = limits.resolve(Size::ZERO); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: crate::core::Event, + layout: Layout<'_>, + cursor: Cursor, + _renderer: &crate::Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, + ) -> Status { + let bounds = layout.bounds(); + + let custom_shader_event = match event { + core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), + core::Event::Keyboard(keyboard_event) => { + Some(Event::Keyboard(keyboard_event)) + } + core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), + _ => None, + }; + + if let Some(custom_shader_event) = custom_shader_event { + let state = tree.state.downcast_mut::(); + + let (event_status, message) = self.program.update( + state, + custom_shader_event, + bounds, + cursor, + shell, + ); + + if let Some(message) = message { + shell.publish(message); + } + + return event_status; + } + + event::Status::Ignored + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: Cursor, + _viewport: &Rectangle, + _renderer: &crate::Renderer, + ) -> mouse::Interaction { + let bounds = layout.bounds(); + let state = tree.state.downcast_ref::(); + + self.program.mouse_interaction(state, bounds, cursor) + } + + fn draw( + &self, + tree: &widget::Tree, + renderer: &mut crate::Renderer, + _theme: &Theme, + _style: &Style, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + _viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + let state = tree.state.downcast_ref::(); + + renderer.draw_custom( + bounds, + self.program.draw(state, cursor_position, bounds), + ); + } +} + +impl<'a, M, P, Theme> From> + for Element<'a, M, crate::Renderer> +where + M: 'a, + P: Program + 'a, +{ + fn from(custom: Shader) -> Element<'a, M, crate::Renderer> { + Element::new(custom) + } +} + +impl Program for &T +where + T: Program, +{ + type State = T::State; + type Primitive = T::Primitive; + + fn update( + &self, + state: &mut Self::State, + event: Event, + bounds: Rectangle, + cursor: Cursor, + shell: &mut Shell<'_, Message>, + ) -> (Status, Option) { + T::update(self, state, event, bounds, cursor, shell) + } + + fn draw( + &self, + state: &Self::State, + cursor: Cursor, + bounds: Rectangle, + ) -> Self::Primitive { + T::draw(self, state, cursor, bounds) + } + + fn mouse_interaction( + &self, + state: &Self::State, + bounds: Rectangle, + cursor: Cursor, + ) -> Interaction { + T::mouse_interaction(self, state, bounds, cursor) + } +} diff --git a/renderer/src/widget/shader/event.rs b/renderer/src/widget/shader/event.rs new file mode 100644 index 00000000..981b30d7 --- /dev/null +++ b/renderer/src/widget/shader/event.rs @@ -0,0 +1,21 @@ +//! Handle events of a custom shader widget. +use crate::core::keyboard; +use crate::core::mouse; +use crate::core::touch; + +pub use crate::core::event::Status; + +/// A [`Shader`] event. +/// +/// [`Shader`]: crate::widget::shader::Shader; +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Event { + /// A mouse event. + Mouse(mouse::Event), + + /// A touch event. + Touch(touch::Event), + + /// A keyboard event. + Keyboard(keyboard::Event), +} diff --git a/renderer/src/widget/shader/program.rs b/renderer/src/widget/shader/program.rs new file mode 100644 index 00000000..b8871688 --- /dev/null +++ b/renderer/src/widget/shader/program.rs @@ -0,0 +1,60 @@ +use crate::core::{event, mouse, Rectangle, Shell}; +use crate::widget; +use widget::shader; + +/// The state and logic of a [`Shader`] widget. +/// +/// A [`Program`] can mutate the internal state of a [`Shader`] widget +/// and produce messages for an application. +/// +/// [`Shader`]: crate::widget::shader::Shader +pub trait Program { + /// The internal state of the [`Program`]. + type State: Default + 'static; + + /// The type of primitive this [`Program`] can draw. + type Primitive: shader::Primitive + 'static; + + /// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes + /// based on mouse & other events. You can use the [`Shell`] to publish messages, request a + /// redraw for the window, etc. + /// + /// By default, this method does and returns nothing. + /// + /// [`State`]: Self::State + fn update( + &self, + _state: &mut Self::State, + _event: shader::Event, + _bounds: Rectangle, + _cursor: mouse::Cursor, + _shell: &mut Shell<'_, Message>, + ) -> (event::Status, Option) { + (event::Status::Ignored, None) + } + + /// Draws the [`Primitive`]. + /// + /// [`Primitive`]: Self::Primitive + fn draw( + &self, + state: &Self::State, + cursor: mouse::Cursor, + bounds: Rectangle, + ) -> Self::Primitive; + + /// Returns the current mouse interaction of the [`Program`]. + /// + /// The interaction returned will be in effect even if the cursor position is out of + /// bounds of the [`Shader`]'s program. + /// + /// [`Shader`]: crate::widget::shader::Shader + fn mouse_interaction( + &self, + _state: &Self::State, + _bounds: Rectangle, + _cursor: mouse::Cursor, + ) -> mouse::Interaction { + mouse::Interaction::default() + } +} diff --git a/style/src/theme.rs b/style/src/theme.rs index 47010728..cc31d72d 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -1,7 +1,7 @@ //! Use the built-in theme and styles. pub mod palette; -pub use palette::Palette; +pub use self::palette::Palette; use crate::application; use crate::button; diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 2bd29f42..907611d9 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -3,9 +3,7 @@ use crate::graphics::backend; use crate::graphics::color; use crate::graphics::{Transformation, Viewport}; use crate::primitive::{self, Primitive}; -use crate::quad; -use crate::text; -use crate::triangle; +use crate::{custom, quad, text, triangle}; use crate::{Layer, Settings}; #[cfg(feature = "tracing")] @@ -25,6 +23,7 @@ pub struct Backend { quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, triangle_pipeline: triangle::Pipeline, + pipeline_storage: custom::Storage, #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, @@ -50,6 +49,7 @@ impl Backend { quad_pipeline, text_pipeline, triangle_pipeline, + pipeline_storage: custom::Storage::default(), #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, @@ -66,6 +66,7 @@ impl Backend { queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, clear_color: Option, + format: wgpu::TextureFormat, frame: &wgpu::TextureView, primitives: &[Primitive], viewport: &Viewport, @@ -88,6 +89,7 @@ impl Backend { self.prepare( device, queue, + format, encoder, scale_factor, target_size, @@ -117,6 +119,7 @@ impl Backend { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, + format: wgpu::TextureFormat, _encoder: &mut wgpu::CommandEncoder, scale_factor: f32, target_size: Size, @@ -179,6 +182,20 @@ impl Backend { target_size, ); } + + if !layer.shaders.is_empty() { + for shader in &layer.shaders { + shader.primitive.prepare( + format, + device, + queue, + target_size, + scale_factor, + transformation, + &mut self.pipeline_storage, + ); + } + } } } @@ -302,6 +319,47 @@ impl Backend { text_layer += 1; } + + // kill render pass to let custom shaders get mut access to encoder + let _ = ManuallyDrop::into_inner(render_pass); + + if !layer.shaders.is_empty() { + for shader in &layer.shaders { + //This extra check is needed since each custom pipeline must set it's own + //scissor rect, which will panic if bounds.w/h < 1 + let bounds = shader.bounds * scale_factor; + + if bounds.width < 1.0 || bounds.height < 1.0 { + continue; + } + + shader.primitive.render( + &self.pipeline_storage, + bounds.into(), + target, + target_size, + encoder, + ); + } + } + + // recreate and continue processing layers + render_pass = ManuallyDrop::new(encoder.begin_render_pass( + &wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::quad render pass"), + color_attachments: &[Some( + wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + }, + )], + depth_stencil_attachment: None, + }, + )); } let _ = ManuallyDrop::into_inner(render_pass); diff --git a/wgpu/src/custom.rs b/wgpu/src/custom.rs new file mode 100644 index 00000000..65dd0496 --- /dev/null +++ b/wgpu/src/custom.rs @@ -0,0 +1,66 @@ +use crate::core::{Rectangle, Size}; +use crate::graphics::Transformation; +use std::any::{Any, TypeId}; +use std::collections::HashMap; +use std::fmt::Debug; + +/// Stores custom, user-provided pipelines. +#[derive(Default, Debug)] +pub struct Storage { + pipelines: HashMap>, +} + +impl Storage { + /// Returns `true` if `Storage` contains a pipeline with type `T`. + pub fn has(&self) -> bool { + self.pipelines.get(&TypeId::of::()).is_some() + } + + /// Inserts the pipeline `T` in to [`Storage`]. + pub fn store(&mut self, pipeline: T) { + let _ = self.pipelines.insert(TypeId::of::(), Box::new(pipeline)); + } + + /// Returns a reference to pipeline with type `T` if it exists in [`Storage`]. + pub fn get(&self) -> Option<&T> { + self.pipelines.get(&TypeId::of::()).map(|pipeline| { + pipeline + .downcast_ref::() + .expect("Pipeline with this type does not exist in Storage.") + }) + } + + /// Returns a mutable reference to pipeline `T` if it exists in [`Storage`]. + pub fn get_mut(&mut self) -> Option<&mut T> { + self.pipelines.get_mut(&TypeId::of::()).map(|pipeline| { + pipeline + .downcast_mut::() + .expect("Pipeline with this type does not exist in Storage.") + }) + } +} + +/// A set of methods which allows a [`Primitive`] to be rendered. +pub trait Primitive: Debug + Send + Sync + 'static { + /// Processes the [`Primitive`], allowing for GPU buffer allocation. + fn prepare( + &self, + format: wgpu::TextureFormat, + device: &wgpu::Device, + queue: &wgpu::Queue, + target_size: Size, + scale_factor: f32, + transform: Transformation, + storage: &mut Storage, + ); + + /// Renders the [`Primitive`]. + fn render( + &self, + storage: &Storage, + bounds: Rectangle, + target: &wgpu::TextureView, + target_size: Size, + encoder: &mut wgpu::CommandEncoder, + ); +} diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 286801e6..d451cbfd 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -34,6 +34,9 @@ pub struct Layer<'a> { /// The images of the [`Layer`]. pub images: Vec, + + /// The custom shader primitives of this [`Layer`]. + pub shaders: Vec, } impl<'a> Layer<'a> { @@ -45,6 +48,7 @@ impl<'a> Layer<'a> { meshes: Vec::new(), text: Vec::new(), images: Vec::new(), + shaders: Vec::new(), } } @@ -308,6 +312,18 @@ impl<'a> Layer<'a> { } } }, + primitive::Custom::Shader(shader) => { + let layer = &mut layers[current_layer]; + + let bounds = Rectangle::new( + Point::new(translation.x, translation.y), + shader.bounds.size(), + ); + + if layer.bounds.intersection(&bounds).is_some() { + layer.shaders.push(shader.clone()); + } + } }, } } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 424dfeb3..13d8e886 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -29,6 +29,7 @@ rustdoc::broken_intra_doc_links )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] +pub mod custom; pub mod layer; pub mod primitive; pub mod settings; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 8dbf3008..4347dcda 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,6 +1,10 @@ //! Draw using different graphical primitives. use crate::core::Rectangle; +use crate::custom; use crate::graphics::{Damage, Mesh}; +use std::any::Any; +use std::fmt::Debug; +use std::sync::Arc; /// The graphical primitives supported by `iced_wgpu`. pub type Primitive = crate::graphics::Primitive; @@ -10,12 +14,44 @@ pub type Primitive = crate::graphics::Primitive; pub enum Custom { /// A mesh primitive. Mesh(Mesh), + /// A custom shader primitive + Shader(Shader), +} + +impl Custom { + /// Create a custom [`Shader`] primitive. + pub fn shader( + bounds: Rectangle, + primitive: P, + ) -> Self { + Self::Shader(Shader { + bounds, + primitive: Arc::new(primitive), + }) + } } impl Damage for Custom { fn bounds(&self) -> Rectangle { match self { Self::Mesh(mesh) => mesh.bounds(), + Self::Shader(shader) => shader.bounds, } } } + +#[derive(Clone, Debug)] +/// A custom primitive which can be used to render primitives associated with a custom pipeline. +pub struct Shader { + /// The bounds of the [`Shader`]. + pub bounds: Rectangle, + + /// The [`custom::Primitive`] to render. + pub primitive: Arc, +} + +impl PartialEq for Shader { + fn eq(&self, other: &Self) -> bool { + self.primitive.type_id() == other.primitive.type_id() + } +} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 1ddbe5fe..90d64e17 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -178,6 +178,7 @@ pub fn present>( &compositor.queue, &mut encoder, Some(background_color), + frame.texture.format(), view, primitives, viewport, @@ -357,6 +358,7 @@ pub fn screenshot>( &compositor.queue, &mut encoder, Some(background_color), + texture.format(), &view, primitives, viewport, diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 6d62c181..032f801c 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -20,6 +20,7 @@ image = ["iced_renderer/image"] svg = ["iced_renderer/svg"] canvas = ["iced_renderer/geometry"] qr_code = ["canvas", "qrcode"] +wgpu = [] [dependencies] iced_renderer.workspace = true diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 2f959370..e052f398 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -97,6 +97,9 @@ pub use tooltip::Tooltip; #[doc(no_inline)] pub use vertical_slider::VerticalSlider; +#[cfg(feature = "wgpu")] +pub use renderer::widget::shader::{self, Shader}; + #[cfg(feature = "svg")] pub mod svg; -- cgit From 36e85215932079fa324cfefb620602ad79f7df3d Mon Sep 17 00:00:00 2001 From: Bingus Date: Mon, 18 Sep 2023 09:04:28 -0700 Subject: Removed `Into` for Rectangle from u32 --- core/src/rectangle.rs | 11 ----------- wgpu/src/backend.rs | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index d5437d51..c1c2eeac 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -183,17 +183,6 @@ impl From> for Rectangle { } } -impl From> for Rectangle { - fn from(rectangle: Rectangle) -> Self { - Rectangle { - x: rectangle.x as u32, - y: rectangle.y as u32, - width: rectangle.width as u32, - height: rectangle.height as u32, - } - } -} - impl std::ops::Add> for Rectangle where T: std::ops::Add, diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 907611d9..ace2ef95 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -335,7 +335,7 @@ impl Backend { shader.primitive.render( &self.pipeline_storage, - bounds.into(), + bounds.snap(), target, target_size, encoder, -- cgit From 91fca024b629b7ddf4de533a75f01593954362f6 Mon Sep 17 00:00:00 2001 From: Bingus Date: Thu, 21 Sep 2023 13:24:54 -0700 Subject: Reexport Transformation from widget::shader --- renderer/src/widget/shader.rs | 1 + widget/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/renderer/src/widget/shader.rs b/renderer/src/widget/shader.rs index da42a7dd..85fc13c8 100644 --- a/renderer/src/widget/shader.rs +++ b/renderer/src/widget/shader.rs @@ -18,6 +18,7 @@ pub use event::Event; pub use iced_wgpu::custom::Primitive; pub use iced_wgpu::custom::Storage; pub use program::Program; +pub use iced_graphics::Transformation; /// A widget which can render custom shaders with Iced's `wgpu` backend. /// diff --git a/widget/src/lib.rs b/widget/src/lib.rs index e052f398..5220e83a 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -98,7 +98,7 @@ pub use tooltip::Tooltip; pub use vertical_slider::VerticalSlider; #[cfg(feature = "wgpu")] -pub use renderer::widget::shader::{self, Shader}; +pub use renderer::widget::shader::{self, Shader, Transformation}; #[cfg(feature = "svg")] pub mod svg; -- cgit From 65f4ff060a36c6dc3afea20d75f541d72460d333 Mon Sep 17 00:00:00 2001 From: Bingus Date: Thu, 28 Sep 2023 09:48:38 -0700 Subject: Added redraw request handling to widget events. --- renderer/src/widget/shader.rs | 8 ++++---- renderer/src/widget/shader/event.rs | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/renderer/src/widget/shader.rs b/renderer/src/widget/shader.rs index 85fc13c8..385fa981 100644 --- a/renderer/src/widget/shader.rs +++ b/renderer/src/widget/shader.rs @@ -5,10 +5,7 @@ use crate::core::mouse::{Cursor, Interaction}; use crate::core::renderer::Style; use crate::core::widget::tree::{State, Tag}; use crate::core::widget::{tree, Tree}; -use crate::core::{ - self, layout, mouse, widget, Clipboard, Element, Layout, Length, Rectangle, - Shell, Size, Widget, -}; +use crate::core::{self, layout, mouse, widget, Clipboard, Element, Layout, Length, Rectangle, Shell, Size, Widget, window}; use std::marker::PhantomData; mod event; @@ -109,6 +106,9 @@ where Some(Event::Keyboard(keyboard_event)) } core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), + core::Event::Window(window::Event::RedrawRequested(instant)) => { + Some(Event::RedrawRequested(instant)) + } _ => None, }; diff --git a/renderer/src/widget/shader/event.rs b/renderer/src/widget/shader/event.rs index 981b30d7..c1696580 100644 --- a/renderer/src/widget/shader/event.rs +++ b/renderer/src/widget/shader/event.rs @@ -1,4 +1,5 @@ //! Handle events of a custom shader widget. +use std::time::Instant; use crate::core::keyboard; use crate::core::mouse; use crate::core::touch; @@ -18,4 +19,7 @@ pub enum Event { /// A keyboard event. Keyboard(keyboard::Event), + + /// A window requested a redraw. + RedrawRequested(Instant), } -- cgit From de9420e7df7d909bca611c360182dec54c5b1aae Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 11:33:04 +0100 Subject: Fix latest `wgpu` changes --- wgpu/src/backend.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index ace2ef95..27ef0b3c 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -353,11 +353,13 @@ impl Backend { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, }, )], depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, }, )); } -- cgit From 46a48af97fa472e1158e07d4deb988c5601197e0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 11:34:15 +0100 Subject: Write missing documentation for `custom` module in `iced_wgpu` --- wgpu/src/custom.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wgpu/src/custom.rs b/wgpu/src/custom.rs index 65dd0496..65a6f133 100644 --- a/wgpu/src/custom.rs +++ b/wgpu/src/custom.rs @@ -1,3 +1,4 @@ +//! Draw custom primitives. use crate::core::{Rectangle, Size}; use crate::graphics::Transformation; use std::any::{Any, TypeId}; -- cgit From 3e8ed05356dde17a6e31a0dc927a3c19b593b09a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 11:38:16 +0100 Subject: Update `wgpu` in `custom_shader` example --- examples/custom_shader/Cargo.toml | 15 ++++++++++----- examples/custom_shader/src/pipeline.rs | 10 +++++++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/examples/custom_shader/Cargo.toml b/examples/custom_shader/Cargo.toml index 7a927811..0b8466a9 100644 --- a/examples/custom_shader/Cargo.toml +++ b/examples/custom_shader/Cargo.toml @@ -5,9 +5,14 @@ authors = ["Bingus "] edition = "2021" [dependencies] -iced = { path = "../..", features = ["debug", "advanced"]} -image = { version = "0.24.6"} -wgpu = "0.17" -bytemuck = { version = "1.13.1" } -glam = { version = "0.24.0", features = ["bytemuck"] } +iced.workspace = true +iced.features = ["debug", "advanced"] + +image.workspace = true +wgpu.workspace = true +bytemuck.workspace = true + +glam.workspace = true +glam.features = ["bytemuck"] + rand = "0.8.5" diff --git a/examples/custom_shader/src/pipeline.rs b/examples/custom_shader/src/pipeline.rs index 9dd154e8..eef1081d 100644 --- a/examples/custom_shader/src/pipeline.rs +++ b/examples/custom_shader/src/pipeline.rs @@ -355,7 +355,7 @@ impl Pipeline { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, }, )], @@ -364,11 +364,13 @@ impl Pipeline { view: &self.depth_view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), - store: true, + store: wgpu::StoreOp::Store, }), stencil_ops: None, }, ), + timestamp_writes: None, + occlusion_query_set: None, }); pass.set_scissor_rect( @@ -547,7 +549,7 @@ impl DepthPipeline { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some( @@ -557,6 +559,8 @@ impl DepthPipeline { stencil_ops: None, }, ), + timestamp_writes: None, + occlusion_query_set: None, }); pass.set_scissor_rect(bounds.x, bounds.y, bounds.width, bounds.height); -- cgit From 33f626294452aefd1cd04f455fa1d1dfcb7f549e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 11:43:38 +0100 Subject: Fix `clippy` lints :crab: --- examples/custom_shader/src/cubes.rs | 2 +- examples/custom_shader/src/pipeline.rs | 8 ++++---- examples/custom_shader/src/primitive.rs | 4 ++-- renderer/src/lib.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/custom_shader/src/cubes.rs b/examples/custom_shader/src/cubes.rs index 8dbba4b1..00e608e3 100644 --- a/examples/custom_shader/src/cubes.rs +++ b/examples/custom_shader/src/cubes.rs @@ -65,7 +65,7 @@ impl Cubes { let new_len = self.cubes.len() - cubes_2_cut as usize; self.cubes.truncate(new_len); } - _ => {} + Ordering::Equal => {} } } } diff --git a/examples/custom_shader/src/pipeline.rs b/examples/custom_shader/src/pipeline.rs index eef1081d..44ad17a2 100644 --- a/examples/custom_shader/src/pipeline.rs +++ b/examples/custom_shader/src/pipeline.rs @@ -479,15 +479,15 @@ impl DepthPipeline { entry_point: "vs_main", buffers: &[], }, - primitive: Default::default(), + primitive: wgpu::PrimitiveState::default(), depth_stencil: Some(wgpu::DepthStencilState { format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: false, depth_compare: wgpu::CompareFunction::Less, - stencil: Default::default(), - bias: Default::default(), + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), }), - multisample: Default::default(), + multisample: wgpu::MultisampleState::default(), fragment: Some(wgpu::FragmentState { module: &shader, entry_point: "fs_main", diff --git a/examples/custom_shader/src/primitive.rs b/examples/custom_shader/src/primitive.rs index 2201218f..f81ce95d 100644 --- a/examples/custom_shader/src/primitive.rs +++ b/examples/custom_shader/src/primitive.rs @@ -56,7 +56,7 @@ impl shader::Primitive for Primitive { storage: &mut shader::Storage, ) { if !storage.has::() { - storage.store(Pipeline::new(device, queue, format, target_size)) + storage.store(Pipeline::new(device, queue, format, target_size)); } let pipeline = storage.get_mut::().unwrap(); @@ -90,6 +90,6 @@ impl shader::Primitive for Primitive { bounds, self.cubes.len() as u32, self.show_depth_buffer, - ) + ); } } diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 8c5ee2f0..e4b1eda9 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -76,7 +76,7 @@ impl Renderer { Renderer::Wgpu(renderer) => { renderer.draw_primitive(iced_wgpu::Primitive::Custom( iced_wgpu::primitive::Custom::shader(bounds, primitive), - )) + )); } } } -- cgit From 226eac35c3fa35be328f6390fdf2a52a38ed2b0f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 11:51:04 +0100 Subject: Remove old `widget` modules in `iced_renderer` --- renderer/src/widget.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/renderer/src/widget.rs b/renderer/src/widget.rs index 0422c99c..4b7dad5d 100644 --- a/renderer/src/widget.rs +++ b/renderer/src/widget.rs @@ -1,14 +1,2 @@ -#[cfg(feature = "canvas")] -pub mod canvas; - -#[cfg(feature = "canvas")] -pub use canvas::Canvas; - -#[cfg(feature = "qr_code")] -pub mod qr_code; - -#[cfg(feature = "qr_code")] -pub use qr_code::QRCode; - #[cfg(feature = "wgpu")] pub mod shader; -- cgit From 2dda9132cda6d2a2279759f3447bae4e1c277555 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 11:52:34 +0100 Subject: Run `cargo fmt` --- renderer/src/widget/shader.rs | 7 +++++-- renderer/src/widget/shader/event.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/renderer/src/widget/shader.rs b/renderer/src/widget/shader.rs index 385fa981..e218f35a 100644 --- a/renderer/src/widget/shader.rs +++ b/renderer/src/widget/shader.rs @@ -5,17 +5,20 @@ use crate::core::mouse::{Cursor, Interaction}; use crate::core::renderer::Style; use crate::core::widget::tree::{State, Tag}; use crate::core::widget::{tree, Tree}; -use crate::core::{self, layout, mouse, widget, Clipboard, Element, Layout, Length, Rectangle, Shell, Size, Widget, window}; +use crate::core::{ + self, layout, mouse, widget, window, Clipboard, Element, Layout, Length, + Rectangle, Shell, Size, Widget, +}; use std::marker::PhantomData; mod event; mod program; pub use event::Event; +pub use iced_graphics::Transformation; pub use iced_wgpu::custom::Primitive; pub use iced_wgpu::custom::Storage; pub use program::Program; -pub use iced_graphics::Transformation; /// A widget which can render custom shaders with Iced's `wgpu` backend. /// diff --git a/renderer/src/widget/shader/event.rs b/renderer/src/widget/shader/event.rs index c1696580..8901fb31 100644 --- a/renderer/src/widget/shader/event.rs +++ b/renderer/src/widget/shader/event.rs @@ -1,8 +1,8 @@ //! Handle events of a custom shader widget. -use std::time::Instant; use crate::core::keyboard; use crate::core::mouse; use crate::core::touch; +use std::time::Instant; pub use crate::core::event::Status; -- cgit From 9489e29e6619b14ed9f41a8887c4b34158266f71 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 12:49:49 +0100 Subject: Re-organize `custom` module as `pipeline` module ... and move `Shader` widget to `iced_widget` crate --- examples/custom_shader/src/main.rs | 11 +- examples/custom_shader/src/primitive.rs | 15 ++- renderer/src/lib.rs | 44 ++++--- renderer/src/widget.rs | 2 - renderer/src/widget/shader.rs | 219 -------------------------------- renderer/src/widget/shader/event.rs | 25 ---- renderer/src/widget/shader/program.rs | 60 --------- wgpu/src/backend.rs | 29 +++-- wgpu/src/custom.rs | 63 +-------- wgpu/src/layer.rs | 19 ++- wgpu/src/lib.rs | 1 - wgpu/src/primitive.rs | 43 ++----- wgpu/src/primitive/pipeline.rs | 117 +++++++++++++++++ widget/src/lib.rs | 6 +- widget/src/shader.rs | 219 ++++++++++++++++++++++++++++++++ widget/src/shader/event.rs | 26 ++++ widget/src/shader/program.rs | 62 +++++++++ 17 files changed, 505 insertions(+), 456 deletions(-) delete mode 100644 renderer/src/widget.rs delete mode 100644 renderer/src/widget/shader.rs delete mode 100644 renderer/src/widget/shader/event.rs delete mode 100644 renderer/src/widget/shader/program.rs create mode 100644 wgpu/src/primitive/pipeline.rs create mode 100644 widget/src/shader.rs create mode 100644 widget/src/shader/event.rs create mode 100644 widget/src/shader/program.rs diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index 76fa1625..f6370f46 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -3,15 +3,20 @@ mod cubes; mod pipeline; mod primitive; +use crate::camera::Camera; use crate::cubes::Cubes; +use crate::pipeline::Pipeline; + +use iced::executor; +use iced::time::Instant; use iced::widget::{ checkbox, column, container, row, slider, text, vertical_space, Shader, }; +use iced::window; use iced::{ - executor, window, Alignment, Application, Color, Command, Element, Length, - Renderer, Subscription, Theme, + Alignment, Application, Color, Command, Element, Length, Renderer, + Subscription, Theme, }; -use std::time::Instant; fn main() -> iced::Result { IcedCubes::run(iced::Settings::default()) diff --git a/examples/custom_shader/src/primitive.rs b/examples/custom_shader/src/primitive.rs index f81ce95d..520ceb8e 100644 --- a/examples/custom_shader/src/primitive.rs +++ b/examples/custom_shader/src/primitive.rs @@ -4,17 +4,18 @@ pub mod vertex; mod buffer; mod uniforms; -use crate::camera::Camera; -use crate::pipeline::Pipeline; -use crate::primitive::cube::Cube; +pub use buffer::Buffer; +pub use cube::Cube; +pub use uniforms::Uniforms; +pub use vertex::Vertex; + +use crate::Camera; +use crate::Pipeline; + use iced::advanced::graphics::Transformation; use iced::widget::shader; use iced::{Color, Rectangle, Size}; -pub use crate::primitive::vertex::Vertex; -pub use buffer::Buffer; -pub use uniforms::Uniforms; - /// A collection of `Cube`s that can be rendered. #[derive(Debug)] pub struct Primitive { diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index e4b1eda9..1fc4c86b 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -1,13 +1,15 @@ #![forbid(rust_2018_idioms)] #![deny(unsafe_code, unused_results, rustdoc::broken_intra_doc_links)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#[cfg(feature = "wgpu")] +pub use iced_wgpu as wgpu; + pub mod compositor; #[cfg(feature = "geometry")] pub mod geometry; mod settings; -pub mod widget; pub use iced_graphics as graphics; pub use iced_graphics::core; @@ -60,26 +62,6 @@ impl Renderer { } } } - - pub fn draw_custom( - &mut self, - bounds: Rectangle, - primitive: P, - ) { - match self { - Renderer::TinySkia(_) => { - log::warn!( - "Custom shader primitive is unavailable with tiny-skia." - ); - } - #[cfg(feature = "wgpu")] - Renderer::Wgpu(renderer) => { - renderer.draw_primitive(iced_wgpu::Primitive::Custom( - iced_wgpu::primitive::Custom::shader(bounds, primitive), - )); - } - } - } } impl core::Renderer for Renderer { @@ -292,3 +274,23 @@ impl crate::graphics::geometry::Renderer for Renderer { } } } + +#[cfg(feature = "wgpu")] +impl iced_wgpu::primitive::pipeline::Renderer for Renderer { + fn draw_pipeline_primitive( + &mut self, + bounds: Rectangle, + primitive: impl wgpu::primitive::pipeline::Primitive, + ) { + match self { + Self::TinySkia(_renderer) => { + log::warn!( + "Custom shader primitive is unavailable with tiny-skia." + ); + } + Self::Wgpu(renderer) => { + renderer.draw_pipeline_primitive(bounds, primitive); + } + } + } +} diff --git a/renderer/src/widget.rs b/renderer/src/widget.rs deleted file mode 100644 index 4b7dad5d..00000000 --- a/renderer/src/widget.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(feature = "wgpu")] -pub mod shader; diff --git a/renderer/src/widget/shader.rs b/renderer/src/widget/shader.rs deleted file mode 100644 index e218f35a..00000000 --- a/renderer/src/widget/shader.rs +++ /dev/null @@ -1,219 +0,0 @@ -//! A custom shader widget for wgpu applications. -use crate::core::event::Status; -use crate::core::layout::{Limits, Node}; -use crate::core::mouse::{Cursor, Interaction}; -use crate::core::renderer::Style; -use crate::core::widget::tree::{State, Tag}; -use crate::core::widget::{tree, Tree}; -use crate::core::{ - self, layout, mouse, widget, window, Clipboard, Element, Layout, Length, - Rectangle, Shell, Size, Widget, -}; -use std::marker::PhantomData; - -mod event; -mod program; - -pub use event::Event; -pub use iced_graphics::Transformation; -pub use iced_wgpu::custom::Primitive; -pub use iced_wgpu::custom::Storage; -pub use program::Program; - -/// A widget which can render custom shaders with Iced's `wgpu` backend. -/// -/// Must be initialized with a [`Program`], which describes the internal widget state & how -/// its [`Program::Primitive`]s are drawn. -#[allow(missing_debug_implementations)] -pub struct Shader> { - width: Length, - height: Length, - program: P, - _message: PhantomData, -} - -impl> Shader { - /// Create a new custom [`Shader`]. - pub fn new(program: P) -> Self { - Self { - width: Length::Fixed(100.0), - height: Length::Fixed(100.0), - program, - _message: PhantomData, - } - } - - /// Set the `width` of the custom [`Shader`]. - pub fn width(mut self, width: impl Into) -> Self { - self.width = width.into(); - self - } - - /// Set the `height` of the custom [`Shader`]. - pub fn height(mut self, height: impl Into) -> Self { - self.height = height.into(); - self - } -} - -impl Widget> - for Shader -where - P: Program, -{ - fn tag(&self) -> Tag { - struct Tag(T); - tree::Tag::of::>() - } - - fn state(&self) -> State { - tree::State::new(P::State::default()) - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - _tree: &mut Tree, - _renderer: &crate::Renderer, - limits: &Limits, - ) -> Node { - let limits = limits.width(self.width).height(self.height); - let size = limits.resolve(Size::ZERO); - - layout::Node::new(size) - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: crate::core::Event, - layout: Layout<'_>, - cursor: Cursor, - _renderer: &crate::Renderer, - _clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - _viewport: &Rectangle, - ) -> Status { - let bounds = layout.bounds(); - - let custom_shader_event = match event { - core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), - core::Event::Keyboard(keyboard_event) => { - Some(Event::Keyboard(keyboard_event)) - } - core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), - core::Event::Window(window::Event::RedrawRequested(instant)) => { - Some(Event::RedrawRequested(instant)) - } - _ => None, - }; - - if let Some(custom_shader_event) = custom_shader_event { - let state = tree.state.downcast_mut::(); - - let (event_status, message) = self.program.update( - state, - custom_shader_event, - bounds, - cursor, - shell, - ); - - if let Some(message) = message { - shell.publish(message); - } - - return event_status; - } - - event::Status::Ignored - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor: Cursor, - _viewport: &Rectangle, - _renderer: &crate::Renderer, - ) -> mouse::Interaction { - let bounds = layout.bounds(); - let state = tree.state.downcast_ref::(); - - self.program.mouse_interaction(state, bounds, cursor) - } - - fn draw( - &self, - tree: &widget::Tree, - renderer: &mut crate::Renderer, - _theme: &Theme, - _style: &Style, - layout: Layout<'_>, - cursor_position: mouse::Cursor, - _viewport: &Rectangle, - ) { - let bounds = layout.bounds(); - let state = tree.state.downcast_ref::(); - - renderer.draw_custom( - bounds, - self.program.draw(state, cursor_position, bounds), - ); - } -} - -impl<'a, M, P, Theme> From> - for Element<'a, M, crate::Renderer> -where - M: 'a, - P: Program + 'a, -{ - fn from(custom: Shader) -> Element<'a, M, crate::Renderer> { - Element::new(custom) - } -} - -impl Program for &T -where - T: Program, -{ - type State = T::State; - type Primitive = T::Primitive; - - fn update( - &self, - state: &mut Self::State, - event: Event, - bounds: Rectangle, - cursor: Cursor, - shell: &mut Shell<'_, Message>, - ) -> (Status, Option) { - T::update(self, state, event, bounds, cursor, shell) - } - - fn draw( - &self, - state: &Self::State, - cursor: Cursor, - bounds: Rectangle, - ) -> Self::Primitive { - T::draw(self, state, cursor, bounds) - } - - fn mouse_interaction( - &self, - state: &Self::State, - bounds: Rectangle, - cursor: Cursor, - ) -> Interaction { - T::mouse_interaction(self, state, bounds, cursor) - } -} diff --git a/renderer/src/widget/shader/event.rs b/renderer/src/widget/shader/event.rs deleted file mode 100644 index 8901fb31..00000000 --- a/renderer/src/widget/shader/event.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Handle events of a custom shader widget. -use crate::core::keyboard; -use crate::core::mouse; -use crate::core::touch; -use std::time::Instant; - -pub use crate::core::event::Status; - -/// A [`Shader`] event. -/// -/// [`Shader`]: crate::widget::shader::Shader; -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Event { - /// A mouse event. - Mouse(mouse::Event), - - /// A touch event. - Touch(touch::Event), - - /// A keyboard event. - Keyboard(keyboard::Event), - - /// A window requested a redraw. - RedrawRequested(Instant), -} diff --git a/renderer/src/widget/shader/program.rs b/renderer/src/widget/shader/program.rs deleted file mode 100644 index b8871688..00000000 --- a/renderer/src/widget/shader/program.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::core::{event, mouse, Rectangle, Shell}; -use crate::widget; -use widget::shader; - -/// The state and logic of a [`Shader`] widget. -/// -/// A [`Program`] can mutate the internal state of a [`Shader`] widget -/// and produce messages for an application. -/// -/// [`Shader`]: crate::widget::shader::Shader -pub trait Program { - /// The internal state of the [`Program`]. - type State: Default + 'static; - - /// The type of primitive this [`Program`] can draw. - type Primitive: shader::Primitive + 'static; - - /// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes - /// based on mouse & other events. You can use the [`Shell`] to publish messages, request a - /// redraw for the window, etc. - /// - /// By default, this method does and returns nothing. - /// - /// [`State`]: Self::State - fn update( - &self, - _state: &mut Self::State, - _event: shader::Event, - _bounds: Rectangle, - _cursor: mouse::Cursor, - _shell: &mut Shell<'_, Message>, - ) -> (event::Status, Option) { - (event::Status::Ignored, None) - } - - /// Draws the [`Primitive`]. - /// - /// [`Primitive`]: Self::Primitive - fn draw( - &self, - state: &Self::State, - cursor: mouse::Cursor, - bounds: Rectangle, - ) -> Self::Primitive; - - /// Returns the current mouse interaction of the [`Program`]. - /// - /// The interaction returned will be in effect even if the cursor position is out of - /// bounds of the [`Shader`]'s program. - /// - /// [`Shader`]: crate::widget::shader::Shader - fn mouse_interaction( - &self, - _state: &Self::State, - _bounds: Rectangle, - _cursor: mouse::Cursor, - ) -> mouse::Interaction { - mouse::Interaction::default() - } -} diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 27ef0b3c..f89bcee1 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -2,8 +2,11 @@ use crate::core::{Color, Size}; use crate::graphics::backend; use crate::graphics::color; use crate::graphics::{Transformation, Viewport}; +use crate::primitive::pipeline; use crate::primitive::{self, Primitive}; -use crate::{custom, quad, text, triangle}; +use crate::quad; +use crate::text; +use crate::triangle; use crate::{Layer, Settings}; #[cfg(feature = "tracing")] @@ -23,7 +26,7 @@ pub struct Backend { quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, triangle_pipeline: triangle::Pipeline, - pipeline_storage: custom::Storage, + pipeline_storage: pipeline::Storage, #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, @@ -49,7 +52,7 @@ impl Backend { quad_pipeline, text_pipeline, triangle_pipeline, - pipeline_storage: custom::Storage::default(), + pipeline_storage: pipeline::Storage::default(), #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, @@ -183,9 +186,9 @@ impl Backend { ); } - if !layer.shaders.is_empty() { - for shader in &layer.shaders { - shader.primitive.prepare( + if !layer.pipelines.is_empty() { + for pipeline in &layer.pipelines { + pipeline.primitive.prepare( format, device, queue, @@ -323,19 +326,17 @@ impl Backend { // kill render pass to let custom shaders get mut access to encoder let _ = ManuallyDrop::into_inner(render_pass); - if !layer.shaders.is_empty() { - for shader in &layer.shaders { - //This extra check is needed since each custom pipeline must set it's own - //scissor rect, which will panic if bounds.w/h < 1 - let bounds = shader.bounds * scale_factor; + if !layer.pipelines.is_empty() { + for pipeline in &layer.pipelines { + let bounds = (pipeline.bounds * scale_factor).snap(); - if bounds.width < 1.0 || bounds.height < 1.0 { + if bounds.width < 1 || bounds.height < 1 { continue; } - shader.primitive.render( + pipeline.primitive.render( &self.pipeline_storage, - bounds.snap(), + bounds, target, target_size, encoder, diff --git a/wgpu/src/custom.rs b/wgpu/src/custom.rs index 65a6f133..98e2b396 100644 --- a/wgpu/src/custom.rs +++ b/wgpu/src/custom.rs @@ -1,67 +1,8 @@ //! Draw custom primitives. use crate::core::{Rectangle, Size}; use crate::graphics::Transformation; +use crate::primitive; + use std::any::{Any, TypeId}; use std::collections::HashMap; use std::fmt::Debug; - -/// Stores custom, user-provided pipelines. -#[derive(Default, Debug)] -pub struct Storage { - pipelines: HashMap>, -} - -impl Storage { - /// Returns `true` if `Storage` contains a pipeline with type `T`. - pub fn has(&self) -> bool { - self.pipelines.get(&TypeId::of::()).is_some() - } - - /// Inserts the pipeline `T` in to [`Storage`]. - pub fn store(&mut self, pipeline: T) { - let _ = self.pipelines.insert(TypeId::of::(), Box::new(pipeline)); - } - - /// Returns a reference to pipeline with type `T` if it exists in [`Storage`]. - pub fn get(&self) -> Option<&T> { - self.pipelines.get(&TypeId::of::()).map(|pipeline| { - pipeline - .downcast_ref::() - .expect("Pipeline with this type does not exist in Storage.") - }) - } - - /// Returns a mutable reference to pipeline `T` if it exists in [`Storage`]. - pub fn get_mut(&mut self) -> Option<&mut T> { - self.pipelines.get_mut(&TypeId::of::()).map(|pipeline| { - pipeline - .downcast_mut::() - .expect("Pipeline with this type does not exist in Storage.") - }) - } -} - -/// A set of methods which allows a [`Primitive`] to be rendered. -pub trait Primitive: Debug + Send + Sync + 'static { - /// Processes the [`Primitive`], allowing for GPU buffer allocation. - fn prepare( - &self, - format: wgpu::TextureFormat, - device: &wgpu::Device, - queue: &wgpu::Queue, - target_size: Size, - scale_factor: f32, - transform: Transformation, - storage: &mut Storage, - ); - - /// Renders the [`Primitive`]. - fn render( - &self, - storage: &Storage, - bounds: Rectangle, - target: &wgpu::TextureView, - target_size: Size, - encoder: &mut wgpu::CommandEncoder, - ); -} diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index d451cbfd..33aaf670 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -35,8 +35,8 @@ pub struct Layer<'a> { /// The images of the [`Layer`]. pub images: Vec, - /// The custom shader primitives of this [`Layer`]. - pub shaders: Vec, + /// The custom pipelines of this [`Layer`]. + pub pipelines: Vec, } impl<'a> Layer<'a> { @@ -48,7 +48,7 @@ impl<'a> Layer<'a> { meshes: Vec::new(), text: Vec::new(), images: Vec::new(), - shaders: Vec::new(), + pipelines: Vec::new(), } } @@ -312,16 +312,21 @@ impl<'a> Layer<'a> { } } }, - primitive::Custom::Shader(shader) => { + primitive::Custom::Pipeline(pipeline) => { let layer = &mut layers[current_layer]; let bounds = Rectangle::new( Point::new(translation.x, translation.y), - shader.bounds.size(), + pipeline.bounds.size(), ); - if layer.bounds.intersection(&bounds).is_some() { - layer.shaders.push(shader.clone()); + if let Some(clip_bounds) = + layer.bounds.intersection(&bounds) + { + layer.pipelines.push(primitive::Pipeline { + bounds: clip_bounds, + primitive: pipeline.primitive.clone(), + }); } } }, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 13d8e886..424dfeb3 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -29,7 +29,6 @@ rustdoc::broken_intra_doc_links )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] -pub mod custom; pub mod layer; pub mod primitive; pub mod settings; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 4347dcda..fff927ea 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,10 +1,12 @@ //! Draw using different graphical primitives. +pub mod pipeline; + +pub use pipeline::Pipeline; + use crate::core::Rectangle; -use crate::custom; use crate::graphics::{Damage, Mesh}; -use std::any::Any; + use std::fmt::Debug; -use std::sync::Arc; /// The graphical primitives supported by `iced_wgpu`. pub type Primitive = crate::graphics::Primitive; @@ -14,44 +16,15 @@ pub type Primitive = crate::graphics::Primitive; pub enum Custom { /// A mesh primitive. Mesh(Mesh), - /// A custom shader primitive - Shader(Shader), -} - -impl Custom { - /// Create a custom [`Shader`] primitive. - pub fn shader( - bounds: Rectangle, - primitive: P, - ) -> Self { - Self::Shader(Shader { - bounds, - primitive: Arc::new(primitive), - }) - } + /// A custom pipeline primitive. + Pipeline(Pipeline), } impl Damage for Custom { fn bounds(&self) -> Rectangle { match self { Self::Mesh(mesh) => mesh.bounds(), - Self::Shader(shader) => shader.bounds, + Self::Pipeline(pipeline) => pipeline.bounds, } } } - -#[derive(Clone, Debug)] -/// A custom primitive which can be used to render primitives associated with a custom pipeline. -pub struct Shader { - /// The bounds of the [`Shader`]. - pub bounds: Rectangle, - - /// The [`custom::Primitive`] to render. - pub primitive: Arc, -} - -impl PartialEq for Shader { - fn eq(&self, other: &Self) -> bool { - self.primitive.type_id() == other.primitive.type_id() - } -} diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs new file mode 100644 index 00000000..b59aeff1 --- /dev/null +++ b/wgpu/src/primitive/pipeline.rs @@ -0,0 +1,117 @@ +//! Draw primitives using custom pipelines. +use crate::core::{Rectangle, Size}; +use crate::graphics::Transformation; + +use std::any::{Any, TypeId}; +use std::collections::HashMap; +use std::fmt::Debug; +use std::sync::Arc; + +#[derive(Clone, Debug)] +/// A custom primitive which can be used to render primitives associated with a custom pipeline. +pub struct Pipeline { + /// The bounds of the [`Shader`]. + pub bounds: Rectangle, + + /// The [`custom::Primitive`] to render. + pub primitive: Arc, +} + +impl Pipeline { + /// Creates a new [`Pipeline`] with the given [`Primitive`]. + pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self { + Pipeline { + bounds, + primitive: Arc::new(primitive), + } + } +} + +impl PartialEq for Pipeline { + fn eq(&self, other: &Self) -> bool { + self.primitive.type_id() == other.primitive.type_id() + } +} + +/// A set of methods which allows a [`Primitive`] to be rendered. +pub trait Primitive: Debug + Send + Sync + 'static { + /// Processes the [`Primitive`], allowing for GPU buffer allocation. + fn prepare( + &self, + format: wgpu::TextureFormat, + device: &wgpu::Device, + queue: &wgpu::Queue, + target_size: Size, + scale_factor: f32, + transform: Transformation, + storage: &mut Storage, + ); + + /// Renders the [`Primitive`]. + fn render( + &self, + storage: &Storage, + bounds: Rectangle, + target: &wgpu::TextureView, + target_size: Size, + encoder: &mut wgpu::CommandEncoder, + ); +} + +/// A renderer than can draw custom pipeline primitives. +pub trait Renderer: crate::core::Renderer { + /// Draws a custom pipeline primitive. + fn draw_pipeline_primitive( + &mut self, + bounds: Rectangle, + primitive: impl Primitive, + ); +} + +impl Renderer for crate::Renderer { + fn draw_pipeline_primitive( + &mut self, + bounds: Rectangle, + primitive: impl Primitive, + ) { + self.draw_primitive(super::Primitive::Custom(super::Custom::Pipeline( + Pipeline::new(bounds, primitive), + ))); + } +} + +/// Stores custom, user-provided pipelines. +#[derive(Default, Debug)] +pub struct Storage { + pipelines: HashMap>, +} + +impl Storage { + /// Returns `true` if `Storage` contains a pipeline with type `T`. + pub fn has(&self) -> bool { + self.pipelines.get(&TypeId::of::()).is_some() + } + + /// Inserts the pipeline `T` in to [`Storage`]. + pub fn store(&mut self, pipeline: T) { + let _ = self.pipelines.insert(TypeId::of::(), Box::new(pipeline)); + } + + /// Returns a reference to pipeline with type `T` if it exists in [`Storage`]. + pub fn get(&self) -> Option<&T> { + self.pipelines.get(&TypeId::of::()).map(|pipeline| { + pipeline + .downcast_ref::() + .expect("Pipeline with this type does not exist in Storage.") + }) + } + + /// Returns a mutable reference to pipeline `T` if it exists in [`Storage`]. + pub fn get_mut(&mut self) -> Option<&mut T> { + self.pipelines.get_mut(&TypeId::of::()).map(|pipeline| { + pipeline + .downcast_mut::() + .expect("Pipeline with this type does not exist in Storage.") + }) + } +} diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 5220e83a..07378d83 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -98,7 +98,11 @@ pub use tooltip::Tooltip; pub use vertical_slider::VerticalSlider; #[cfg(feature = "wgpu")] -pub use renderer::widget::shader::{self, Shader, Transformation}; +pub mod shader; + +#[cfg(feature = "wgpu")] +#[doc(no_inline)] +pub use shader::Shader; #[cfg(feature = "svg")] pub mod svg; diff --git a/widget/src/shader.rs b/widget/src/shader.rs new file mode 100644 index 00000000..9d482537 --- /dev/null +++ b/widget/src/shader.rs @@ -0,0 +1,219 @@ +//! A custom shader widget for wgpu applications. +mod event; +mod program; + +pub use event::Event; +pub use program::Program; + +use crate::core; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::widget::tree::{self, Tree}; +use crate::core::widget::{self, Widget}; +use crate::core::window; +use crate::core::{Clipboard, Element, Length, Rectangle, Shell, Size}; +use crate::renderer::wgpu::primitive::pipeline; + +use std::marker::PhantomData; + +pub use pipeline::{Primitive, Storage}; + +/// A widget which can render custom shaders with Iced's `wgpu` backend. +/// +/// Must be initialized with a [`Program`], which describes the internal widget state & how +/// its [`Program::Primitive`]s are drawn. +#[allow(missing_debug_implementations)] +pub struct Shader> { + width: Length, + height: Length, + program: P, + _message: PhantomData, +} + +impl> Shader { + /// Create a new custom [`Shader`]. + pub fn new(program: P) -> Self { + Self { + width: Length::Fixed(100.0), + height: Length::Fixed(100.0), + program, + _message: PhantomData, + } + } + + /// Set the `width` of the custom [`Shader`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Set the `height` of the custom [`Shader`]. + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } +} + +impl Widget for Shader +where + P: Program, + Renderer: pipeline::Renderer, +{ + fn tag(&self) -> tree::Tag { + struct Tag(T); + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(P::State::default()) + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + _tree: &mut Tree, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width).height(self.height); + let size = limits.resolve(Size::ZERO); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: crate::core::Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, + ) -> event::Status { + let bounds = layout.bounds(); + + let custom_shader_event = match event { + core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), + core::Event::Keyboard(keyboard_event) => { + Some(Event::Keyboard(keyboard_event)) + } + core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), + core::Event::Window(window::Event::RedrawRequested(instant)) => { + Some(Event::RedrawRequested(instant)) + } + _ => None, + }; + + if let Some(custom_shader_event) = custom_shader_event { + let state = tree.state.downcast_mut::(); + + let (event_status, message) = self.program.update( + state, + custom_shader_event, + bounds, + cursor, + shell, + ); + + if let Some(message) = message { + shell.publish(message); + } + + return event_status; + } + + event::Status::Ignored + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + let bounds = layout.bounds(); + let state = tree.state.downcast_ref::(); + + self.program.mouse_interaction(state, bounds, cursor) + } + + fn draw( + &self, + tree: &widget::Tree, + renderer: &mut Renderer, + _theme: &Renderer::Theme, + _style: &renderer::Style, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + _viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + let state = tree.state.downcast_ref::(); + + renderer.draw_pipeline_primitive( + bounds, + self.program.draw(state, cursor_position, bounds), + ); + } +} + +impl<'a, Message, Renderer, P> From> + for Element<'a, Message, Renderer> +where + Message: 'a, + Renderer: pipeline::Renderer, + P: Program + 'a, +{ + fn from(custom: Shader) -> Element<'a, Message, Renderer> { + Element::new(custom) + } +} + +impl Program for &T +where + T: Program, +{ + type State = T::State; + type Primitive = T::Primitive; + + fn update( + &self, + state: &mut Self::State, + event: Event, + bounds: Rectangle, + cursor: mouse::Cursor, + shell: &mut Shell<'_, Message>, + ) -> (event::Status, Option) { + T::update(self, state, event, bounds, cursor, shell) + } + + fn draw( + &self, + state: &Self::State, + cursor: mouse::Cursor, + bounds: Rectangle, + ) -> Self::Primitive { + T::draw(self, state, cursor, bounds) + } + + fn mouse_interaction( + &self, + state: &Self::State, + bounds: Rectangle, + cursor: mouse::Cursor, + ) -> mouse::Interaction { + T::mouse_interaction(self, state, bounds, cursor) + } +} diff --git a/widget/src/shader/event.rs b/widget/src/shader/event.rs new file mode 100644 index 00000000..e4d2b03d --- /dev/null +++ b/widget/src/shader/event.rs @@ -0,0 +1,26 @@ +//! Handle events of a custom shader widget. +use crate::core::keyboard; +use crate::core::mouse; +use crate::core::touch; + +use std::time::Instant; + +pub use crate::core::event::Status; + +/// A [`Shader`] event. +/// +/// [`Shader`]: crate::widget::shader::Shader; +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Event { + /// A mouse event. + Mouse(mouse::Event), + + /// A touch event. + Touch(touch::Event), + + /// A keyboard event. + Keyboard(keyboard::Event), + + /// A window requested a redraw. + RedrawRequested(Instant), +} diff --git a/widget/src/shader/program.rs b/widget/src/shader/program.rs new file mode 100644 index 00000000..0319844d --- /dev/null +++ b/widget/src/shader/program.rs @@ -0,0 +1,62 @@ +use crate::core::event; +use crate::core::mouse; +use crate::core::{Rectangle, Shell}; +use crate::renderer::wgpu::primitive::pipeline; +use crate::shader; + +/// The state and logic of a [`Shader`] widget. +/// +/// A [`Program`] can mutate the internal state of a [`Shader`] widget +/// and produce messages for an application. +/// +/// [`Shader`]: crate::widget::shader::Shader +pub trait Program { + /// The internal state of the [`Program`]. + type State: Default + 'static; + + /// The type of primitive this [`Program`] can draw. + type Primitive: pipeline::Primitive + 'static; + + /// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes + /// based on mouse & other events. You can use the [`Shell`] to publish messages, request a + /// redraw for the window, etc. + /// + /// By default, this method does and returns nothing. + /// + /// [`State`]: Self::State + fn update( + &self, + _state: &mut Self::State, + _event: shader::Event, + _bounds: Rectangle, + _cursor: mouse::Cursor, + _shell: &mut Shell<'_, Message>, + ) -> (event::Status, Option) { + (event::Status::Ignored, None) + } + + /// Draws the [`Primitive`]. + /// + /// [`Primitive`]: Self::Primitive + fn draw( + &self, + state: &Self::State, + cursor: mouse::Cursor, + bounds: Rectangle, + ) -> Self::Primitive; + + /// Returns the current mouse interaction of the [`Program`]. + /// + /// The interaction returned will be in effect even if the cursor position is out of + /// bounds of the [`Shader`]'s program. + /// + /// [`Shader`]: crate::widget::shader::Shader + fn mouse_interaction( + &self, + _state: &Self::State, + _bounds: Rectangle, + _cursor: mouse::Cursor, + ) -> mouse::Interaction { + mouse::Interaction::default() + } +} -- cgit From 882ae304ac8da899d7d83aed433b7dd3311a0496 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 12:51:08 +0100 Subject: Enable `iced_renderer/wgpu` feature in `iced_widget` --- widget/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 032f801c..e8e363c4 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -20,7 +20,7 @@ image = ["iced_renderer/image"] svg = ["iced_renderer/svg"] canvas = ["iced_renderer/geometry"] qr_code = ["canvas", "qrcode"] -wgpu = [] +wgpu = ["iced_renderer/wgpu"] [dependencies] iced_renderer.workspace = true -- cgit From c2baf18cbff721e25c3103b6235292530b419c54 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 12:52:03 +0100 Subject: Use `Instant` from `iced_core` instead of `std` This is needed for Wasm compatibility. --- widget/src/shader/event.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/widget/src/shader/event.rs b/widget/src/shader/event.rs index e4d2b03d..a5a7acaa 100644 --- a/widget/src/shader/event.rs +++ b/widget/src/shader/event.rs @@ -1,10 +1,9 @@ //! Handle events of a custom shader widget. use crate::core::keyboard; use crate::core::mouse; +use crate::core::time::Instant; use crate::core::touch; -use std::time::Instant; - pub use crate::core::event::Status; /// A [`Shader`] event. -- cgit From 280d3736d59b62c4087fe980c187953cc2be83d2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 13:23:28 +0100 Subject: Fix broken intra-doc links --- wgpu/src/primitive/pipeline.rs | 4 ++-- widget/src/shader/event.rs | 2 +- widget/src/shader/program.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs index b59aeff1..5dbd6697 100644 --- a/wgpu/src/primitive/pipeline.rs +++ b/wgpu/src/primitive/pipeline.rs @@ -10,10 +10,10 @@ use std::sync::Arc; #[derive(Clone, Debug)] /// A custom primitive which can be used to render primitives associated with a custom pipeline. pub struct Pipeline { - /// The bounds of the [`Shader`]. + /// The bounds of the [`Pipeline`]. pub bounds: Rectangle, - /// The [`custom::Primitive`] to render. + /// The [`Primitive`] to render. pub primitive: Arc, } diff --git a/widget/src/shader/event.rs b/widget/src/shader/event.rs index a5a7acaa..1cc484fb 100644 --- a/widget/src/shader/event.rs +++ b/widget/src/shader/event.rs @@ -8,7 +8,7 @@ pub use crate::core::event::Status; /// A [`Shader`] event. /// -/// [`Shader`]: crate::widget::shader::Shader; +/// [`Shader`]: crate::Shader #[derive(Debug, Clone, Copy, PartialEq)] pub enum Event { /// A mouse event. diff --git a/widget/src/shader/program.rs b/widget/src/shader/program.rs index 0319844d..6dd50404 100644 --- a/widget/src/shader/program.rs +++ b/widget/src/shader/program.rs @@ -9,7 +9,7 @@ use crate::shader; /// A [`Program`] can mutate the internal state of a [`Shader`] widget /// and produce messages for an application. /// -/// [`Shader`]: crate::widget::shader::Shader +/// [`Shader`]: crate::Shader pub trait Program { /// The internal state of the [`Program`]. type State: Default + 'static; @@ -50,7 +50,7 @@ pub trait Program { /// The interaction returned will be in effect even if the cursor position is out of /// bounds of the [`Shader`]'s program. /// - /// [`Shader`]: crate::widget::shader::Shader + /// [`Shader`]: crate::Shader fn mouse_interaction( &self, _state: &Self::State, -- cgit From 91d7df52cdedd1b5c431fdb51a6356e827765b60 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 13:25:49 +0100 Subject: Create `shader` function helper in `iced_widget` --- examples/custom_shader/src/main.rs | 7 +++---- widget/src/helpers.rs | 11 +++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index f6370f46..e9b6776f 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -10,7 +10,7 @@ use crate::pipeline::Pipeline; use iced::executor; use iced::time::Instant; use iced::widget::{ - checkbox, column, container, row, slider, text, vertical_space, Shader, + checkbox, column, container, row, shader, slider, text, vertical_space, }; use iced::window; use iced::{ @@ -150,9 +150,8 @@ impl Application for IcedCubes { .spacing(10) .align_items(Alignment::Center); - let shader = Shader::new(&self.cubes) - .width(Length::Fill) - .height(Length::Fill); + let shader = + shader(&self.cubes).width(Length::Fill).height(Length::Fill); container( column![shader, controls, vertical_space(20),] diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index e0b58722..115198fb 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -385,6 +385,17 @@ where crate::Canvas::new(program) } +/// Creates a new [`Shader`]. +/// +/// [`Shader`]: crate::Shader +#[cfg(feature = "wgpu")] +pub fn shader(program: P) -> crate::Shader +where + P: crate::shader::Program, +{ + crate::Shader::new(program) +} + /// Focuses the previous focusable widget. pub fn focus_previous() -> Command where -- cgit From 63f36b04638f14af3455ead8b82d581a438a28a3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 14:04:54 +0100 Subject: Export `wgpu` crate in `shader` module in `iced_widget` --- examples/custom_shader/Cargo.toml | 1 - examples/custom_shader/src/main.rs | 1 + examples/custom_shader/src/pipeline.rs | 4 +++- examples/custom_shader/src/primitive.rs | 1 + examples/custom_shader/src/primitive/buffer.rs | 2 ++ examples/custom_shader/src/primitive/cube.rs | 2 ++ examples/custom_shader/src/primitive/vertex.rs | 2 ++ widget/src/shader.rs | 1 + 8 files changed, 12 insertions(+), 2 deletions(-) diff --git a/examples/custom_shader/Cargo.toml b/examples/custom_shader/Cargo.toml index 0b8466a9..b602f98d 100644 --- a/examples/custom_shader/Cargo.toml +++ b/examples/custom_shader/Cargo.toml @@ -9,7 +9,6 @@ iced.workspace = true iced.features = ["debug", "advanced"] image.workspace = true -wgpu.workspace = true bytemuck.workspace = true glam.workspace = true diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index e9b6776f..e7b07d78 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -9,6 +9,7 @@ use crate::pipeline::Pipeline; use iced::executor; use iced::time::Instant; +use iced::widget::shader::wgpu; use iced::widget::{ checkbox, column, container, row, shader, slider, text, vertical_space, }; diff --git a/examples/custom_shader/src/pipeline.rs b/examples/custom_shader/src/pipeline.rs index 44ad17a2..9343e5e0 100644 --- a/examples/custom_shader/src/pipeline.rs +++ b/examples/custom_shader/src/pipeline.rs @@ -1,8 +1,10 @@ use crate::primitive; use crate::primitive::cube; use crate::primitive::{Buffer, Uniforms}; +use crate::wgpu; +use crate::wgpu::util::DeviceExt; + use iced::{Rectangle, Size}; -use wgpu::util::DeviceExt; const SKY_TEXTURE_SIZE: u32 = 128; diff --git a/examples/custom_shader/src/primitive.rs b/examples/custom_shader/src/primitive.rs index 520ceb8e..f5862ab3 100644 --- a/examples/custom_shader/src/primitive.rs +++ b/examples/custom_shader/src/primitive.rs @@ -9,6 +9,7 @@ pub use cube::Cube; pub use uniforms::Uniforms; pub use vertex::Vertex; +use crate::wgpu; use crate::Camera; use crate::Pipeline; diff --git a/examples/custom_shader/src/primitive/buffer.rs b/examples/custom_shader/src/primitive/buffer.rs index 377ce1bb..ef4c41c9 100644 --- a/examples/custom_shader/src/primitive/buffer.rs +++ b/examples/custom_shader/src/primitive/buffer.rs @@ -1,3 +1,5 @@ +use crate::wgpu; + // A custom buffer container for dynamic resizing. pub struct Buffer { pub raw: wgpu::Buffer, diff --git a/examples/custom_shader/src/primitive/cube.rs b/examples/custom_shader/src/primitive/cube.rs index c23f2132..7ece685d 100644 --- a/examples/custom_shader/src/primitive/cube.rs +++ b/examples/custom_shader/src/primitive/cube.rs @@ -1,4 +1,6 @@ use crate::primitive::Vertex; +use crate::wgpu; + use glam::{vec2, vec3, Vec3}; use rand::{thread_rng, Rng}; diff --git a/examples/custom_shader/src/primitive/vertex.rs b/examples/custom_shader/src/primitive/vertex.rs index 6d17aa0f..e64cd926 100644 --- a/examples/custom_shader/src/primitive/vertex.rs +++ b/examples/custom_shader/src/primitive/vertex.rs @@ -1,3 +1,5 @@ +use crate::wgpu; + #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[repr(C)] pub struct Vertex { diff --git a/widget/src/shader.rs b/widget/src/shader.rs index 9d482537..fe6214db 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -17,6 +17,7 @@ use crate::renderer::wgpu::primitive::pipeline; use std::marker::PhantomData; +pub use crate::renderer::wgpu::wgpu; pub use pipeline::{Primitive, Storage}; /// A widget which can render custom shaders with Iced's `wgpu` backend. -- cgit From 78a06384b1e6fc5e0c472dc19169fcaf11fe27b2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 14:36:38 +0100 Subject: Use a single source for amount of cubes in `custom_shader` example --- examples/custom_shader/src/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index e7b07d78..3336e7f5 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -26,7 +26,6 @@ fn main() -> iced::Result { struct IcedCubes { start: Instant, cubes: Cubes, - num_cubes_slider: u32, } impl Default for IcedCubes { @@ -34,7 +33,6 @@ impl Default for IcedCubes { Self { start: Instant::now(), cubes: Cubes::new(), - num_cubes_slider: cubes::MAX, } } } @@ -65,7 +63,6 @@ impl Application for IcedCubes { fn update(&mut self, message: Self::Message) -> Command { match message { Message::CubeAmountChanged(num) => { - self.num_cubes_slider = num; self.cubes.adjust_num_cubes(num); } Message::CubeSizeChanged(size) => { @@ -91,7 +88,7 @@ impl Application for IcedCubes { "Amount", slider( 1..=cubes::MAX, - self.num_cubes_slider, + self.cubes.cubes.len() as u32, Message::CubeAmountChanged ) .width(100) -- cgit From 9ddfaf3ee73cef3e7bd122f4d11893f77647c2c3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 14:41:48 +0100 Subject: Rename `cubes` to `scene` in `custom_shader` example --- examples/custom_shader/src/cubes.rs | 99 ------------------------------------- examples/custom_shader/src/main.rs | 42 ++++++++-------- examples/custom_shader/src/scene.rs | 99 +++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 120 deletions(-) delete mode 100644 examples/custom_shader/src/cubes.rs create mode 100644 examples/custom_shader/src/scene.rs diff --git a/examples/custom_shader/src/cubes.rs b/examples/custom_shader/src/cubes.rs deleted file mode 100644 index 00e608e3..00000000 --- a/examples/custom_shader/src/cubes.rs +++ /dev/null @@ -1,99 +0,0 @@ -use crate::camera::Camera; -use crate::primitive; -use crate::primitive::cube::Cube; -use glam::Vec3; -use iced::widget::shader; -use iced::{mouse, Color, Rectangle}; -use rand::Rng; -use std::cmp::Ordering; -use std::iter; -use std::time::Duration; - -pub const MAX: u32 = 500; - -#[derive(Clone)] -pub struct Cubes { - pub size: f32, - pub cubes: Vec, - pub camera: Camera, - pub show_depth_buffer: bool, - pub light_color: Color, -} - -impl Cubes { - pub fn new() -> Self { - let mut cubes = Self { - size: 0.2, - cubes: vec![], - camera: Camera::default(), - show_depth_buffer: false, - light_color: Color::WHITE, - }; - - cubes.adjust_num_cubes(MAX); - - cubes - } - - pub fn update(&mut self, time: Duration) { - for cube in self.cubes.iter_mut() { - cube.update(self.size, time.as_secs_f32()); - } - } - - pub fn adjust_num_cubes(&mut self, num_cubes: u32) { - let curr_cubes = self.cubes.len() as u32; - - match num_cubes.cmp(&curr_cubes) { - Ordering::Greater => { - // spawn - let cubes_2_spawn = (num_cubes - curr_cubes) as usize; - - let mut cubes = 0; - self.cubes.extend(iter::from_fn(|| { - if cubes < cubes_2_spawn { - cubes += 1; - Some(Cube::new(self.size, rnd_origin())) - } else { - None - } - })); - } - Ordering::Less => { - // chop - let cubes_2_cut = curr_cubes - num_cubes; - let new_len = self.cubes.len() - cubes_2_cut as usize; - self.cubes.truncate(new_len); - } - Ordering::Equal => {} - } - } -} - -impl shader::Program for Cubes { - type State = (); - type Primitive = primitive::Primitive; - - fn draw( - &self, - _state: &Self::State, - _cursor: mouse::Cursor, - bounds: Rectangle, - ) -> Self::Primitive { - primitive::Primitive::new( - &self.cubes, - &self.camera, - bounds, - self.show_depth_buffer, - self.light_color, - ) - } -} - -fn rnd_origin() -> Vec3 { - Vec3::new( - rand::thread_rng().gen_range(-4.0..4.0), - rand::thread_rng().gen_range(-4.0..4.0), - rand::thread_rng().gen_range(-4.0..2.0), - ) -} diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index 3336e7f5..f90061d7 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -1,11 +1,11 @@ mod camera; -mod cubes; mod pipeline; mod primitive; +mod scene; use crate::camera::Camera; -use crate::cubes::Cubes; use crate::pipeline::Pipeline; +use crate::scene::Scene; use iced::executor; use iced::time::Instant; @@ -25,14 +25,14 @@ fn main() -> iced::Result { struct IcedCubes { start: Instant, - cubes: Cubes, + scene: Scene, } impl Default for IcedCubes { fn default() -> Self { Self { start: Instant::now(), - cubes: Cubes::new(), + scene: Scene::new(), } } } @@ -62,20 +62,20 @@ impl Application for IcedCubes { fn update(&mut self, message: Self::Message) -> Command { match message { - Message::CubeAmountChanged(num) => { - self.cubes.adjust_num_cubes(num); + Message::CubeAmountChanged(amount) => { + self.scene.change_amount(amount); } Message::CubeSizeChanged(size) => { - self.cubes.size = size; + self.scene.size = size; } Message::Tick(time) => { - self.cubes.update(time - self.start); + self.scene.update(time - self.start); } Message::ShowDepthBuffer(show) => { - self.cubes.show_depth_buffer = show; + self.scene.show_depth_buffer = show; } Message::LightColorChanged(color) => { - self.cubes.light_color = color; + self.scene.light_color = color; } } @@ -87,21 +87,21 @@ impl Application for IcedCubes { control( "Amount", slider( - 1..=cubes::MAX, - self.cubes.cubes.len() as u32, + 1..=scene::MAX, + self.scene.cubes.len() as u32, Message::CubeAmountChanged ) .width(100) ), control( "Size", - slider(0.1..=0.25, self.cubes.size, Message::CubeSizeChanged) + slider(0.1..=0.25, self.scene.size, Message::CubeSizeChanged) .step(0.01) .width(100), ), checkbox( "Show Depth Buffer", - self.cubes.show_depth_buffer, + self.scene.show_depth_buffer, Message::ShowDepthBuffer ), ] @@ -110,10 +110,10 @@ impl Application for IcedCubes { let bottom_controls = row![ control( "R", - slider(0.0..=1.0, self.cubes.light_color.r, move |r| { + slider(0.0..=1.0, self.scene.light_color.r, move |r| { Message::LightColorChanged(Color { r, - ..self.cubes.light_color + ..self.scene.light_color }) }) .step(0.01) @@ -121,10 +121,10 @@ impl Application for IcedCubes { ), control( "G", - slider(0.0..=1.0, self.cubes.light_color.g, move |g| { + slider(0.0..=1.0, self.scene.light_color.g, move |g| { Message::LightColorChanged(Color { g, - ..self.cubes.light_color + ..self.scene.light_color }) }) .step(0.01) @@ -132,10 +132,10 @@ impl Application for IcedCubes { ), control( "B", - slider(0.0..=1.0, self.cubes.light_color.b, move |b| { + slider(0.0..=1.0, self.scene.light_color.b, move |b| { Message::LightColorChanged(Color { b, - ..self.cubes.light_color + ..self.scene.light_color }) }) .step(0.01) @@ -149,7 +149,7 @@ impl Application for IcedCubes { .align_items(Alignment::Center); let shader = - shader(&self.cubes).width(Length::Fill).height(Length::Fill); + shader(&self.scene).width(Length::Fill).height(Length::Fill); container( column![shader, controls, vertical_space(20),] diff --git a/examples/custom_shader/src/scene.rs b/examples/custom_shader/src/scene.rs new file mode 100644 index 00000000..ab923093 --- /dev/null +++ b/examples/custom_shader/src/scene.rs @@ -0,0 +1,99 @@ +use crate::camera::Camera; +use crate::primitive; +use crate::primitive::cube::Cube; +use glam::Vec3; +use iced::widget::shader; +use iced::{mouse, Color, Rectangle}; +use rand::Rng; +use std::cmp::Ordering; +use std::iter; +use std::time::Duration; + +pub const MAX: u32 = 500; + +#[derive(Clone)] +pub struct Scene { + pub size: f32, + pub cubes: Vec, + pub camera: Camera, + pub show_depth_buffer: bool, + pub light_color: Color, +} + +impl Scene { + pub fn new() -> Self { + let mut scene = Self { + size: 0.2, + cubes: vec![], + camera: Camera::default(), + show_depth_buffer: false, + light_color: Color::WHITE, + }; + + scene.change_amount(MAX); + + scene + } + + pub fn update(&mut self, time: Duration) { + for cube in self.cubes.iter_mut() { + cube.update(self.size, time.as_secs_f32()); + } + } + + pub fn change_amount(&mut self, amount: u32) { + let curr_cubes = self.cubes.len() as u32; + + match amount.cmp(&curr_cubes) { + Ordering::Greater => { + // spawn + let cubes_2_spawn = (amount - curr_cubes) as usize; + + let mut cubes = 0; + self.cubes.extend(iter::from_fn(|| { + if cubes < cubes_2_spawn { + cubes += 1; + Some(Cube::new(self.size, rnd_origin())) + } else { + None + } + })); + } + Ordering::Less => { + // chop + let cubes_2_cut = curr_cubes - amount; + let new_len = self.cubes.len() - cubes_2_cut as usize; + self.cubes.truncate(new_len); + } + Ordering::Equal => {} + } + } +} + +impl shader::Program for Scene { + type State = (); + type Primitive = primitive::Primitive; + + fn draw( + &self, + _state: &Self::State, + _cursor: mouse::Cursor, + bounds: Rectangle, + ) -> Self::Primitive { + primitive::Primitive::new( + &self.cubes, + &self.camera, + bounds, + self.show_depth_buffer, + self.light_color, + ) + } +} + +fn rnd_origin() -> Vec3 { + Vec3::new( + rand::thread_rng().gen_range(-4.0..4.0), + rand::thread_rng().gen_range(-4.0..4.0), + rand::thread_rng().gen_range(-4.0..2.0), + ) +} -- cgit From 34b5cb75ef9f97076dd9e306d8afb68058176883 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 14:43:02 +0100 Subject: Remove `Default` implementation in `custom_shader` example --- examples/custom_shader/src/main.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index f90061d7..f4853507 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -28,15 +28,6 @@ struct IcedCubes { scene: Scene, } -impl Default for IcedCubes { - fn default() -> Self { - Self { - start: Instant::now(), - scene: Scene::new(), - } - } -} - #[derive(Debug, Clone)] enum Message { CubeAmountChanged(u32), @@ -53,7 +44,13 @@ impl Application for IcedCubes { type Flags = (); fn new(_flags: Self::Flags) -> (Self, Command) { - (IcedCubes::default(), Command::none()) + ( + Self { + start: Instant::now(), + scene: Scene::new(), + }, + Command::none(), + ) } fn title(&self) -> String { -- cgit From fee3bf0df4e3099ea74def738be8743b2b72d78a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 14:47:29 +0100 Subject: Kill current render pass only when custom pipelines are present in layer --- wgpu/src/backend.rs | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index f89bcee1..91ae777b 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -323,10 +323,9 @@ impl Backend { text_layer += 1; } - // kill render pass to let custom shaders get mut access to encoder - let _ = ManuallyDrop::into_inner(render_pass); - if !layer.pipelines.is_empty() { + let _ = ManuallyDrop::into_inner(render_pass); + for pipeline in &layer.pipelines { let bounds = (pipeline.bounds * scale_factor).snap(); @@ -342,27 +341,26 @@ impl Backend { encoder, ); } - } - // recreate and continue processing layers - render_pass = ManuallyDrop::new(encoder.begin_render_pass( - &wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::quad render pass"), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, + render_pass = ManuallyDrop::new(encoder.begin_render_pass( + &wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::quad render pass"), + color_attachments: &[Some( + wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, }, - }, - )], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }, - )); + )], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }, + )); + } } let _ = ManuallyDrop::into_inner(render_pass); -- cgit From b1b2467b45e16185cc17df00b4c75700435cd46e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 14:50:57 +0100 Subject: Fix render pass label in `iced_wgpu` --- wgpu/src/backend.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 91ae777b..88caad06 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -222,7 +222,7 @@ impl Backend { let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass( &wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::quad render pass"), + label: Some("iced_wgpu render pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: target, resolve_target: None, @@ -285,7 +285,7 @@ impl Backend { render_pass = ManuallyDrop::new(encoder.begin_render_pass( &wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::quad render pass"), + label: Some("iced_wgpu render pass"), color_attachments: &[Some( wgpu::RenderPassColorAttachment { view: target, @@ -344,7 +344,7 @@ impl Backend { render_pass = ManuallyDrop::new(encoder.begin_render_pass( &wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::quad render pass"), + label: Some("iced_wgpu render pass"), color_attachments: &[Some( wgpu::RenderPassColorAttachment { view: target, -- cgit From 811aa673e9e832ebe38bf56a087f32fdc7aba59c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 15:48:01 +0100 Subject: Improve module hierarchy of `custom_shader` example --- examples/custom_shader/src/camera.rs | 53 -- examples/custom_shader/src/main.rs | 7 +- examples/custom_shader/src/pipeline.rs | 606 -------------------- examples/custom_shader/src/primitive.rs | 97 ---- examples/custom_shader/src/primitive/buffer.rs | 41 -- examples/custom_shader/src/primitive/cube.rs | 326 ----------- examples/custom_shader/src/primitive/uniforms.rs | 22 - examples/custom_shader/src/primitive/vertex.rs | 31 -- examples/custom_shader/src/scene.rs | 103 +++- examples/custom_shader/src/scene/camera.rs | 53 ++ examples/custom_shader/src/scene/pipeline.rs | 615 +++++++++++++++++++++ .../custom_shader/src/scene/pipeline/buffer.rs | 41 ++ examples/custom_shader/src/scene/pipeline/cube.rs | 326 +++++++++++ .../custom_shader/src/scene/pipeline/uniforms.rs | 23 + .../custom_shader/src/scene/pipeline/vertex.rs | 31 ++ widget/src/shader.rs | 1 + 16 files changed, 1186 insertions(+), 1190 deletions(-) delete mode 100644 examples/custom_shader/src/camera.rs delete mode 100644 examples/custom_shader/src/pipeline.rs delete mode 100644 examples/custom_shader/src/primitive.rs delete mode 100644 examples/custom_shader/src/primitive/buffer.rs delete mode 100644 examples/custom_shader/src/primitive/cube.rs delete mode 100644 examples/custom_shader/src/primitive/uniforms.rs delete mode 100644 examples/custom_shader/src/primitive/vertex.rs create mode 100644 examples/custom_shader/src/scene/camera.rs create mode 100644 examples/custom_shader/src/scene/pipeline.rs create mode 100644 examples/custom_shader/src/scene/pipeline/buffer.rs create mode 100644 examples/custom_shader/src/scene/pipeline/cube.rs create mode 100644 examples/custom_shader/src/scene/pipeline/uniforms.rs create mode 100644 examples/custom_shader/src/scene/pipeline/vertex.rs diff --git a/examples/custom_shader/src/camera.rs b/examples/custom_shader/src/camera.rs deleted file mode 100644 index 2a49c102..00000000 --- a/examples/custom_shader/src/camera.rs +++ /dev/null @@ -1,53 +0,0 @@ -use glam::{mat4, vec3, vec4}; -use iced::Rectangle; - -#[derive(Copy, Clone)] -pub struct Camera { - eye: glam::Vec3, - target: glam::Vec3, - up: glam::Vec3, - fov_y: f32, - near: f32, - far: f32, -} - -impl Default for Camera { - fn default() -> Self { - Self { - eye: vec3(0.0, 2.0, 3.0), - target: glam::Vec3::ZERO, - up: glam::Vec3::Y, - fov_y: 45.0, - near: 0.1, - far: 100.0, - } - } -} - -pub const OPENGL_TO_WGPU_MATRIX: glam::Mat4 = mat4( - vec4(1.0, 0.0, 0.0, 0.0), - vec4(0.0, 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 0.5, 0.0), - vec4(0.0, 0.0, 0.5, 1.0), -); - -impl Camera { - pub fn build_view_proj_matrix(&self, bounds: Rectangle) -> glam::Mat4 { - //TODO looks distorted without padding; base on surface texture size instead? - let aspect_ratio = bounds.width / (bounds.height + 150.0); - - let view = glam::Mat4::look_at_rh(self.eye, self.target, self.up); - let proj = glam::Mat4::perspective_rh( - self.fov_y, - aspect_ratio, - self.near, - self.far, - ); - - OPENGL_TO_WGPU_MATRIX * proj * view - } - - pub fn position(&self) -> glam::Vec4 { - glam::Vec4::from((self.eye, 0.0)) - } -} diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index f4853507..2eb1ac4a 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -1,11 +1,6 @@ -mod camera; -mod pipeline; -mod primitive; mod scene; -use crate::camera::Camera; -use crate::pipeline::Pipeline; -use crate::scene::Scene; +use scene::Scene; use iced::executor; use iced::time::Instant; diff --git a/examples/custom_shader/src/pipeline.rs b/examples/custom_shader/src/pipeline.rs deleted file mode 100644 index 9343e5e0..00000000 --- a/examples/custom_shader/src/pipeline.rs +++ /dev/null @@ -1,606 +0,0 @@ -use crate::primitive; -use crate::primitive::cube; -use crate::primitive::{Buffer, Uniforms}; -use crate::wgpu; -use crate::wgpu::util::DeviceExt; - -use iced::{Rectangle, Size}; - -const SKY_TEXTURE_SIZE: u32 = 128; - -pub struct Pipeline { - pipeline: wgpu::RenderPipeline, - vertices: wgpu::Buffer, - cubes: Buffer, - uniforms: wgpu::Buffer, - uniform_bind_group: wgpu::BindGroup, - depth_texture_size: Size, - depth_view: wgpu::TextureView, - depth_pipeline: DepthPipeline, -} - -impl Pipeline { - pub fn new( - device: &wgpu::Device, - queue: &wgpu::Queue, - format: wgpu::TextureFormat, - target_size: Size, - ) -> Self { - //vertices of one cube - let vertices = - device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("cubes vertex buffer"), - contents: bytemuck::cast_slice(&cube::Raw::vertices()), - usage: wgpu::BufferUsages::VERTEX, - }); - - //cube instance data - let cubes_buffer = Buffer::new( - device, - "cubes instance buffer", - std::mem::size_of::() as u64, - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); - - //uniforms for all cubes - let uniforms = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("cubes uniform buffer"), - size: std::mem::size_of::() as u64, - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - //depth buffer - let depth_texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("cubes depth texture"), - size: wgpu::Extent3d { - width: target_size.width, - height: target_size.height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Depth32Float, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }); - - let depth_view = - depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); - - let normal_map_data = load_normal_map_data(); - - //normal map - let normal_texture = device.create_texture_with_data( - queue, - &wgpu::TextureDescriptor { - label: Some("cubes normal map texture"), - size: wgpu::Extent3d { - width: 1024, - height: 1024, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8Unorm, - usage: wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }, - &normal_map_data, - ); - - let normal_view = - normal_texture.create_view(&wgpu::TextureViewDescriptor::default()); - - //skybox texture for reflection/refraction - let skybox_data = load_skybox_data(); - - let skybox_texture = device.create_texture_with_data( - queue, - &wgpu::TextureDescriptor { - label: Some("cubes skybox texture"), - size: wgpu::Extent3d { - width: SKY_TEXTURE_SIZE, - height: SKY_TEXTURE_SIZE, - depth_or_array_layers: 6, //one for each face of the cube - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8Unorm, - usage: wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }, - &skybox_data, - ); - - let sky_view = - skybox_texture.create_view(&wgpu::TextureViewDescriptor { - label: Some("cubes skybox texture view"), - dimension: Some(wgpu::TextureViewDimension::Cube), - ..Default::default() - }); - - let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - label: Some("cubes skybox sampler"), - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, - ..Default::default() - }); - - let uniform_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("cubes uniform bind group layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { - filterable: true, - }, - view_dimension: wgpu::TextureViewDimension::Cube, - multisampled: false, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler( - wgpu::SamplerBindingType::Filtering, - ), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 3, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { - filterable: true, - }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, - ], - }); - - let uniform_bind_group = - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("cubes uniform bind group"), - layout: &uniform_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: uniforms.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView(&sky_view), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::Sampler(&sky_sampler), - }, - wgpu::BindGroupEntry { - binding: 3, - resource: wgpu::BindingResource::TextureView( - &normal_view, - ), - }, - ], - }); - - let layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("cubes pipeline layout"), - bind_group_layouts: &[&uniform_bind_group_layout], - push_constant_ranges: &[], - }); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("cubes shader"), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( - include_str!("shaders/cubes.wgsl"), - )), - }); - - let pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("cubes pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[primitive::Vertex::desc(), cube::Raw::desc()], - }, - primitive: wgpu::PrimitiveState::default(), - depth_stencil: Some(wgpu::DepthStencilState { - format: wgpu::TextureFormat::Depth32Float, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::Less, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }), - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::One, - operation: wgpu::BlendOperation::Max, - }, - }), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - multiview: None, - }); - - let depth_pipeline = DepthPipeline::new( - device, - format, - depth_texture.create_view(&wgpu::TextureViewDescriptor::default()), - ); - - Self { - pipeline, - cubes: cubes_buffer, - uniforms, - uniform_bind_group, - vertices, - depth_texture_size: target_size, - depth_view, - depth_pipeline, - } - } - - fn update_depth_texture(&mut self, device: &wgpu::Device, size: Size) { - if self.depth_texture_size.height != size.height - || self.depth_texture_size.width != size.width - { - let text = device.create_texture(&wgpu::TextureDescriptor { - label: Some("cubes depth texture"), - size: wgpu::Extent3d { - width: size.width, - height: size.height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Depth32Float, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }); - - self.depth_view = - text.create_view(&wgpu::TextureViewDescriptor::default()); - self.depth_texture_size = size; - - self.depth_pipeline.update(device, &text); - } - } - - pub fn update( - &mut self, - device: &wgpu::Device, - queue: &wgpu::Queue, - target_size: Size, - uniforms: &Uniforms, - num_cubes: usize, - cubes: &[cube::Raw], - ) { - //recreate depth texture if surface texture size has changed - self.update_depth_texture(device, target_size); - - // update uniforms - queue.write_buffer(&self.uniforms, 0, bytemuck::bytes_of(uniforms)); - - //resize cubes vertex buffer if cubes amount changed - let new_size = num_cubes * std::mem::size_of::(); - self.cubes.resize(device, new_size as u64); - - //always write new cube data since they are constantly rotating - queue.write_buffer(&self.cubes.raw, 0, bytemuck::cast_slice(cubes)); - } - - pub fn render( - &self, - target: &wgpu::TextureView, - encoder: &mut wgpu::CommandEncoder, - bounds: Rectangle, - num_cubes: u32, - show_depth: bool, - ) { - { - let mut pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("cubes.pipeline.pass"), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - }, - )], - depth_stencil_attachment: Some( - wgpu::RenderPassDepthStencilAttachment { - view: &self.depth_view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - store: wgpu::StoreOp::Store, - }), - stencil_ops: None, - }, - ), - timestamp_writes: None, - occlusion_query_set: None, - }); - - pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); - pass.set_pipeline(&self.pipeline); - pass.set_bind_group(0, &self.uniform_bind_group, &[]); - pass.set_vertex_buffer(0, self.vertices.slice(..)); - pass.set_vertex_buffer(1, self.cubes.raw.slice(..)); - pass.draw(0..36, 0..num_cubes); - } - - if show_depth { - self.depth_pipeline.render(encoder, target, bounds); - } - } -} - -struct DepthPipeline { - pipeline: wgpu::RenderPipeline, - bind_group_layout: wgpu::BindGroupLayout, - bind_group: wgpu::BindGroup, - sampler: wgpu::Sampler, - depth_view: wgpu::TextureView, -} - -impl DepthPipeline { - pub fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - depth_texture: wgpu::TextureView, - ) -> Self { - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - label: Some("cubes.depth_pipeline.sampler"), - ..Default::default() - }); - - let bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("cubes.depth_pipeline.bind_group_layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler( - wgpu::SamplerBindingType::NonFiltering, - ), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { - filterable: false, - }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, - ], - }); - - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("cubes.depth_pipeline.bind_group"), - layout: &bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Sampler(&sampler), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView( - &depth_texture, - ), - }, - ], - }); - - let layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("cubes.depth_pipeline.layout"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("cubes.depth_pipeline.shader"), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( - include_str!("shaders/depth.wgsl"), - )), - }); - - let pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("cubes.depth_pipeline.pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[], - }, - primitive: wgpu::PrimitiveState::default(), - depth_stencil: Some(wgpu::DepthStencilState { - format: wgpu::TextureFormat::Depth32Float, - depth_write_enabled: false, - depth_compare: wgpu::CompareFunction::Less, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }), - multisample: wgpu::MultisampleState::default(), - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - multiview: None, - }); - - Self { - pipeline, - bind_group_layout, - bind_group, - sampler, - depth_view: depth_texture, - } - } - - pub fn update( - &mut self, - device: &wgpu::Device, - depth_texture: &wgpu::Texture, - ) { - self.depth_view = - depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); - - self.bind_group = - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("cubes.depth_pipeline.bind_group"), - layout: &self.bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Sampler(&self.sampler), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView( - &self.depth_view, - ), - }, - ], - }); - } - - pub fn render( - &self, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - bounds: Rectangle, - ) { - let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("cubes.pipeline.depth_pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: Some( - wgpu::RenderPassDepthStencilAttachment { - view: &self.depth_view, - depth_ops: None, - stencil_ops: None, - }, - ), - timestamp_writes: None, - occlusion_query_set: None, - }); - - pass.set_scissor_rect(bounds.x, bounds.y, bounds.width, bounds.height); - pass.set_pipeline(&self.pipeline); - pass.set_bind_group(0, &self.bind_group, &[]); - pass.draw(0..6, 0..1); - } -} - -fn load_skybox_data() -> Vec { - let pos_x: &[u8] = include_bytes!("textures/skybox/pos_x.jpg"); - let neg_x: &[u8] = include_bytes!("textures/skybox/neg_x.jpg"); - let pos_y: &[u8] = include_bytes!("textures/skybox/pos_y.jpg"); - let neg_y: &[u8] = include_bytes!("textures/skybox/neg_y.jpg"); - let pos_z: &[u8] = include_bytes!("textures/skybox/pos_z.jpg"); - let neg_z: &[u8] = include_bytes!("textures/skybox/neg_z.jpg"); - - let data: [&[u8]; 6] = [pos_x, neg_x, pos_y, neg_y, pos_z, neg_z]; - - data.iter().fold(vec![], |mut acc, bytes| { - let i = image::load_from_memory_with_format( - bytes, - image::ImageFormat::Jpeg, - ) - .unwrap() - .to_rgba8() - .into_raw(); - - acc.extend(i); - acc - }) -} - -fn load_normal_map_data() -> Vec { - let bytes: &[u8] = include_bytes!("textures/ice_cube_normal_map.png"); - - image::load_from_memory_with_format(bytes, image::ImageFormat::Png) - .unwrap() - .to_rgba8() - .into_raw() -} diff --git a/examples/custom_shader/src/primitive.rs b/examples/custom_shader/src/primitive.rs deleted file mode 100644 index f5862ab3..00000000 --- a/examples/custom_shader/src/primitive.rs +++ /dev/null @@ -1,97 +0,0 @@ -pub mod cube; -pub mod vertex; - -mod buffer; -mod uniforms; - -pub use buffer::Buffer; -pub use cube::Cube; -pub use uniforms::Uniforms; -pub use vertex::Vertex; - -use crate::wgpu; -use crate::Camera; -use crate::Pipeline; - -use iced::advanced::graphics::Transformation; -use iced::widget::shader; -use iced::{Color, Rectangle, Size}; - -/// A collection of `Cube`s that can be rendered. -#[derive(Debug)] -pub struct Primitive { - cubes: Vec, - uniforms: Uniforms, - show_depth_buffer: bool, -} - -impl Primitive { - pub fn new( - cubes: &[Cube], - camera: &Camera, - bounds: Rectangle, - show_depth_buffer: bool, - light_color: Color, - ) -> Self { - let uniforms = Uniforms::new(camera, bounds, light_color); - - Self { - cubes: cubes - .iter() - .map(cube::Raw::from_cube) - .collect::>(), - uniforms, - show_depth_buffer, - } - } -} - -impl shader::Primitive for Primitive { - fn prepare( - &self, - format: wgpu::TextureFormat, - device: &wgpu::Device, - queue: &wgpu::Queue, - target_size: Size, - _scale_factor: f32, - _transform: Transformation, - storage: &mut shader::Storage, - ) { - if !storage.has::() { - storage.store(Pipeline::new(device, queue, format, target_size)); - } - - let pipeline = storage.get_mut::().unwrap(); - - //upload data to GPU - pipeline.update( - device, - queue, - target_size, - &self.uniforms, - self.cubes.len(), - &self.cubes, - ); - } - - fn render( - &self, - storage: &shader::Storage, - bounds: Rectangle, - target: &wgpu::TextureView, - _target_size: Size, - encoder: &mut wgpu::CommandEncoder, - ) { - //at this point our pipeline should always be initialized - let pipeline = storage.get::().unwrap(); - - //render primitive - pipeline.render( - target, - encoder, - bounds, - self.cubes.len() as u32, - self.show_depth_buffer, - ); - } -} diff --git a/examples/custom_shader/src/primitive/buffer.rs b/examples/custom_shader/src/primitive/buffer.rs deleted file mode 100644 index ef4c41c9..00000000 --- a/examples/custom_shader/src/primitive/buffer.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::wgpu; - -// A custom buffer container for dynamic resizing. -pub struct Buffer { - pub raw: wgpu::Buffer, - label: &'static str, - size: u64, - usage: wgpu::BufferUsages, -} - -impl Buffer { - pub fn new( - device: &wgpu::Device, - label: &'static str, - size: u64, - usage: wgpu::BufferUsages, - ) -> Self { - Self { - raw: device.create_buffer(&wgpu::BufferDescriptor { - label: Some(label), - size, - usage, - mapped_at_creation: false, - }), - label, - size, - usage, - } - } - - pub fn resize(&mut self, device: &wgpu::Device, new_size: u64) { - if new_size > self.size { - self.raw = device.create_buffer(&wgpu::BufferDescriptor { - label: Some(self.label), - size: new_size, - usage: self.usage, - mapped_at_creation: false, - }); - } - } -} diff --git a/examples/custom_shader/src/primitive/cube.rs b/examples/custom_shader/src/primitive/cube.rs deleted file mode 100644 index 7ece685d..00000000 --- a/examples/custom_shader/src/primitive/cube.rs +++ /dev/null @@ -1,326 +0,0 @@ -use crate::primitive::Vertex; -use crate::wgpu; - -use glam::{vec2, vec3, Vec3}; -use rand::{thread_rng, Rng}; - -/// A single instance of a cube. -#[derive(Debug, Clone)] -pub struct Cube { - pub rotation: glam::Quat, - pub position: Vec3, - pub size: f32, - rotation_dir: f32, - rotation_axis: glam::Vec3, -} - -impl Default for Cube { - fn default() -> Self { - Self { - rotation: glam::Quat::IDENTITY, - position: glam::Vec3::ZERO, - size: 0.1, - rotation_dir: 1.0, - rotation_axis: glam::Vec3::Y, - } - } -} - -impl Cube { - pub fn new(size: f32, origin: Vec3) -> Self { - let rnd = thread_rng().gen_range(0.0..=1.0f32); - - Self { - rotation: glam::Quat::IDENTITY, - position: origin + Vec3::new(0.1, 0.1, 0.1), - size, - rotation_dir: if rnd <= 0.5 { -1.0 } else { 1.0 }, - rotation_axis: if rnd <= 0.33 { - glam::Vec3::Y - } else if rnd <= 0.66 { - glam::Vec3::X - } else { - glam::Vec3::Z - }, - } - } - - pub fn update(&mut self, size: f32, time: f32) { - self.rotation = glam::Quat::from_axis_angle( - self.rotation_axis, - time / 2.0 * self.rotation_dir, - ); - self.size = size; - } -} - -#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Debug)] -#[repr(C)] -pub struct Raw { - transformation: glam::Mat4, - normal: glam::Mat3, - _padding: [f32; 3], -} - -impl Raw { - const ATTRIBS: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![ - //cube transformation matrix - 4 => Float32x4, - 5 => Float32x4, - 6 => Float32x4, - 7 => Float32x4, - //normal rotation matrix - 8 => Float32x3, - 9 => Float32x3, - 10 => Float32x3, - ]; - - pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { - wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &Self::ATTRIBS, - } - } -} - -impl Raw { - pub fn from_cube(cube: &Cube) -> Raw { - Raw { - transformation: glam::Mat4::from_scale_rotation_translation( - glam::vec3(cube.size, cube.size, cube.size), - cube.rotation, - cube.position, - ), - normal: glam::Mat3::from_quat(cube.rotation), - _padding: [0.0; 3], - } - } - - pub fn vertices() -> [Vertex; 36] { - [ - //face 1 - Vertex { - pos: vec3(-0.5, -0.5, -0.5), - normal: vec3(0.0, 0.0, -1.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(0.0, 1.0), - }, - Vertex { - pos: vec3(0.5, -0.5, -0.5), - normal: vec3(0.0, 0.0, -1.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(1.0, 1.0), - }, - Vertex { - pos: vec3(0.5, 0.5, -0.5), - normal: vec3(0.0, 0.0, -1.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(1.0, 0.0), - }, - Vertex { - pos: vec3(0.5, 0.5, -0.5), - normal: vec3(0.0, 0.0, -1.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(1.0, 0.0), - }, - Vertex { - pos: vec3(-0.5, 0.5, -0.5), - normal: vec3(0.0, 0.0, -1.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(0.0, 0.0), - }, - Vertex { - pos: vec3(-0.5, -0.5, -0.5), - normal: vec3(0.0, 0.0, -1.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(0.0, 1.0), - }, - //face 2 - Vertex { - pos: vec3(-0.5, -0.5, 0.5), - normal: vec3(0.0, 0.0, 1.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(0.0, 1.0), - }, - Vertex { - pos: vec3(0.5, -0.5, 0.5), - normal: vec3(0.0, 0.0, 1.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(1.0, 1.0), - }, - Vertex { - pos: vec3(0.5, 0.5, 0.5), - normal: vec3(0.0, 0.0, 1.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(1.0, 0.0), - }, - Vertex { - pos: vec3(0.5, 0.5, 0.5), - normal: vec3(0.0, 0.0, 1.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(1.0, 0.0), - }, - Vertex { - pos: vec3(-0.5, 0.5, 0.5), - normal: vec3(0.0, 0.0, 1.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(0.0, 0.0), - }, - Vertex { - pos: vec3(-0.5, -0.5, 0.5), - normal: vec3(0.0, 0.0, 1.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(0.0, 1.0), - }, - //face 3 - Vertex { - pos: vec3(-0.5, 0.5, 0.5), - normal: vec3(-1.0, 0.0, 0.0), - tangent: vec3(0.0, 0.0, -1.0), - uv: vec2(0.0, 1.0), - }, - Vertex { - pos: vec3(-0.5, 0.5, -0.5), - normal: vec3(-1.0, 0.0, 0.0), - tangent: vec3(0.0, 0.0, -1.0), - uv: vec2(1.0, 1.0), - }, - Vertex { - pos: vec3(-0.5, -0.5, -0.5), - normal: vec3(-1.0, 0.0, 0.0), - tangent: vec3(0.0, 0.0, -1.0), - uv: vec2(1.0, 0.0), - }, - Vertex { - pos: vec3(-0.5, -0.5, -0.5), - normal: vec3(-1.0, 0.0, 0.0), - tangent: vec3(0.0, 0.0, -1.0), - uv: vec2(1.0, 0.0), - }, - Vertex { - pos: vec3(-0.5, -0.5, 0.5), - normal: vec3(-1.0, 0.0, 0.0), - tangent: vec3(0.0, 0.0, -1.0), - uv: vec2(0.0, 0.0), - }, - Vertex { - pos: vec3(-0.5, 0.5, 0.5), - normal: vec3(-1.0, 0.0, 0.0), - tangent: vec3(0.0, 0.0, -1.0), - uv: vec2(0.0, 1.0), - }, - //face 4 - Vertex { - pos: vec3(0.5, 0.5, 0.5), - normal: vec3(1.0, 0.0, 0.0), - tangent: vec3(0.0, 0.0, -1.0), - uv: vec2(0.0, 1.0), - }, - Vertex { - pos: vec3(0.5, 0.5, -0.5), - normal: vec3(1.0, 0.0, 0.0), - tangent: vec3(0.0, 0.0, -1.0), - uv: vec2(1.0, 1.0), - }, - Vertex { - pos: vec3(0.5, -0.5, -0.5), - normal: vec3(1.0, 0.0, 0.0), - tangent: vec3(0.0, 0.0, -1.0), - uv: vec2(1.0, 0.0), - }, - Vertex { - pos: vec3(0.5, -0.5, -0.5), - normal: vec3(1.0, 0.0, 0.0), - tangent: vec3(0.0, 0.0, -1.0), - uv: vec2(1.0, 0.0), - }, - Vertex { - pos: vec3(0.5, -0.5, 0.5), - normal: vec3(1.0, 0.0, 0.0), - tangent: vec3(0.0, 0.0, -1.0), - uv: vec2(0.0, 0.0), - }, - Vertex { - pos: vec3(0.5, 0.5, 0.5), - normal: vec3(1.0, 0.0, 0.0), - tangent: vec3(0.0, 0.0, -1.0), - uv: vec2(0.0, 1.0), - }, - //face 5 - Vertex { - pos: vec3(-0.5, -0.5, -0.5), - normal: vec3(0.0, -1.0, 0.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(0.0, 1.0), - }, - Vertex { - pos: vec3(0.5, -0.5, -0.5), - normal: vec3(0.0, -1.0, 0.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(1.0, 1.0), - }, - Vertex { - pos: vec3(0.5, -0.5, 0.5), - normal: vec3(0.0, -1.0, 0.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(1.0, 0.0), - }, - Vertex { - pos: vec3(0.5, -0.5, 0.5), - normal: vec3(0.0, -1.0, 0.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(1.0, 0.0), - }, - Vertex { - pos: vec3(-0.5, -0.5, 0.5), - normal: vec3(0.0, -1.0, 0.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(0.0, 0.0), - }, - Vertex { - pos: vec3(-0.5, -0.5, -0.5), - normal: vec3(0.0, -1.0, 0.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(0.0, 1.0), - }, - //face 6 - Vertex { - pos: vec3(-0.5, 0.5, -0.5), - normal: vec3(0.0, 1.0, 0.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(0.0, 1.0), - }, - Vertex { - pos: vec3(0.5, 0.5, -0.5), - normal: vec3(0.0, 1.0, 0.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(1.0, 1.0), - }, - Vertex { - pos: vec3(0.5, 0.5, 0.5), - normal: vec3(0.0, 1.0, 0.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(1.0, 0.0), - }, - Vertex { - pos: vec3(0.5, 0.5, 0.5), - normal: vec3(0.0, 1.0, 0.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(1.0, 0.0), - }, - Vertex { - pos: vec3(-0.5, 0.5, 0.5), - normal: vec3(0.0, 1.0, 0.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(0.0, 0.0), - }, - Vertex { - pos: vec3(-0.5, 0.5, -0.5), - normal: vec3(0.0, 1.0, 0.0), - tangent: vec3(1.0, 0.0, 0.0), - uv: vec2(0.0, 1.0), - }, - ] - } -} diff --git a/examples/custom_shader/src/primitive/uniforms.rs b/examples/custom_shader/src/primitive/uniforms.rs deleted file mode 100644 index 4fcb413b..00000000 --- a/examples/custom_shader/src/primitive/uniforms.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::camera::Camera; -use iced::{Color, Rectangle}; - -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct Uniforms { - camera_proj: glam::Mat4, - camera_pos: glam::Vec4, - light_color: glam::Vec4, -} - -impl Uniforms { - pub fn new(camera: &Camera, bounds: Rectangle, light_color: Color) -> Self { - let camera_proj = camera.build_view_proj_matrix(bounds); - - Self { - camera_proj, - camera_pos: camera.position(), - light_color: glam::Vec4::from(light_color.into_linear()), - } - } -} diff --git a/examples/custom_shader/src/primitive/vertex.rs b/examples/custom_shader/src/primitive/vertex.rs deleted file mode 100644 index e64cd926..00000000 --- a/examples/custom_shader/src/primitive/vertex.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::wgpu; - -#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct Vertex { - pub pos: glam::Vec3, - pub normal: glam::Vec3, - pub tangent: glam::Vec3, - pub uv: glam::Vec2, -} - -impl Vertex { - const ATTRIBS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![ - //position - 0 => Float32x3, - //normal - 1 => Float32x3, - //tangent - 2 => Float32x3, - //uv - 3 => Float32x2, - ]; - - pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { - wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &Self::ATTRIBS, - } - } -} diff --git a/examples/custom_shader/src/scene.rs b/examples/custom_shader/src/scene.rs index ab923093..3b291ce2 100644 --- a/examples/custom_shader/src/scene.rs +++ b/examples/custom_shader/src/scene.rs @@ -1,13 +1,21 @@ -use crate::camera::Camera; -use crate::primitive; -use crate::primitive::cube::Cube; -use glam::Vec3; +mod camera; +mod pipeline; + +use camera::Camera; +use pipeline::Pipeline; + +use crate::wgpu; +use pipeline::cube::{self, Cube}; + +use iced::mouse; +use iced::time::Duration; use iced::widget::shader; -use iced::{mouse, Color, Rectangle}; +use iced::{Color, Rectangle, Size}; + +use glam::Vec3; use rand::Rng; use std::cmp::Ordering; use std::iter; -use std::time::Duration; pub const MAX: u32 = 500; @@ -72,7 +80,7 @@ impl Scene { impl shader::Program for Scene { type State = (); - type Primitive = primitive::Primitive; + type Primitive = Primitive; fn draw( &self, @@ -80,7 +88,7 @@ impl shader::Program for Scene { _cursor: mouse::Cursor, bounds: Rectangle, ) -> Self::Primitive { - primitive::Primitive::new( + Primitive::new( &self.cubes, &self.camera, bounds, @@ -90,6 +98,85 @@ impl shader::Program for Scene { } } +/// A collection of `Cube`s that can be rendered. +#[derive(Debug)] +pub struct Primitive { + cubes: Vec, + uniforms: pipeline::Uniforms, + show_depth_buffer: bool, +} + +impl Primitive { + pub fn new( + cubes: &[Cube], + camera: &Camera, + bounds: Rectangle, + show_depth_buffer: bool, + light_color: Color, + ) -> Self { + let uniforms = pipeline::Uniforms::new(camera, bounds, light_color); + + Self { + cubes: cubes + .iter() + .map(cube::Raw::from_cube) + .collect::>(), + uniforms, + show_depth_buffer, + } + } +} + +impl shader::Primitive for Primitive { + fn prepare( + &self, + format: wgpu::TextureFormat, + device: &wgpu::Device, + queue: &wgpu::Queue, + target_size: Size, + _scale_factor: f32, + _transform: shader::Transformation, + storage: &mut shader::Storage, + ) { + if !storage.has::() { + storage.store(Pipeline::new(device, queue, format, target_size)); + } + + let pipeline = storage.get_mut::().unwrap(); + + //upload data to GPU + pipeline.update( + device, + queue, + target_size, + &self.uniforms, + self.cubes.len(), + &self.cubes, + ); + } + + fn render( + &self, + storage: &shader::Storage, + bounds: Rectangle, + target: &wgpu::TextureView, + _target_size: Size, + encoder: &mut wgpu::CommandEncoder, + ) { + //at this point our pipeline should always be initialized + let pipeline = storage.get::().unwrap(); + + //render primitive + pipeline.render( + target, + encoder, + bounds, + self.cubes.len() as u32, + self.show_depth_buffer, + ); + } +} + fn rnd_origin() -> Vec3 { Vec3::new( rand::thread_rng().gen_range(-4.0..4.0), diff --git a/examples/custom_shader/src/scene/camera.rs b/examples/custom_shader/src/scene/camera.rs new file mode 100644 index 00000000..2a49c102 --- /dev/null +++ b/examples/custom_shader/src/scene/camera.rs @@ -0,0 +1,53 @@ +use glam::{mat4, vec3, vec4}; +use iced::Rectangle; + +#[derive(Copy, Clone)] +pub struct Camera { + eye: glam::Vec3, + target: glam::Vec3, + up: glam::Vec3, + fov_y: f32, + near: f32, + far: f32, +} + +impl Default for Camera { + fn default() -> Self { + Self { + eye: vec3(0.0, 2.0, 3.0), + target: glam::Vec3::ZERO, + up: glam::Vec3::Y, + fov_y: 45.0, + near: 0.1, + far: 100.0, + } + } +} + +pub const OPENGL_TO_WGPU_MATRIX: glam::Mat4 = mat4( + vec4(1.0, 0.0, 0.0, 0.0), + vec4(0.0, 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 0.5, 0.0), + vec4(0.0, 0.0, 0.5, 1.0), +); + +impl Camera { + pub fn build_view_proj_matrix(&self, bounds: Rectangle) -> glam::Mat4 { + //TODO looks distorted without padding; base on surface texture size instead? + let aspect_ratio = bounds.width / (bounds.height + 150.0); + + let view = glam::Mat4::look_at_rh(self.eye, self.target, self.up); + let proj = glam::Mat4::perspective_rh( + self.fov_y, + aspect_ratio, + self.near, + self.far, + ); + + OPENGL_TO_WGPU_MATRIX * proj * view + } + + pub fn position(&self) -> glam::Vec4 { + glam::Vec4::from((self.eye, 0.0)) + } +} diff --git a/examples/custom_shader/src/scene/pipeline.rs b/examples/custom_shader/src/scene/pipeline.rs new file mode 100644 index 00000000..0967e139 --- /dev/null +++ b/examples/custom_shader/src/scene/pipeline.rs @@ -0,0 +1,615 @@ +pub mod cube; + +mod buffer; +mod uniforms; +mod vertex; + +pub use cube::Cube; +pub use uniforms::Uniforms; + +use buffer::Buffer; +use vertex::Vertex; + +use crate::wgpu; +use crate::wgpu::util::DeviceExt; + +use iced::{Rectangle, Size}; + +const SKY_TEXTURE_SIZE: u32 = 128; + +pub struct Pipeline { + pipeline: wgpu::RenderPipeline, + vertices: wgpu::Buffer, + cubes: Buffer, + uniforms: wgpu::Buffer, + uniform_bind_group: wgpu::BindGroup, + depth_texture_size: Size, + depth_view: wgpu::TextureView, + depth_pipeline: DepthPipeline, +} + +impl Pipeline { + pub fn new( + device: &wgpu::Device, + queue: &wgpu::Queue, + format: wgpu::TextureFormat, + target_size: Size, + ) -> Self { + //vertices of one cube + let vertices = + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("cubes vertex buffer"), + contents: bytemuck::cast_slice(&cube::Raw::vertices()), + usage: wgpu::BufferUsages::VERTEX, + }); + + //cube instance data + let cubes_buffer = Buffer::new( + device, + "cubes instance buffer", + std::mem::size_of::() as u64, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + //uniforms for all cubes + let uniforms = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("cubes uniform buffer"), + size: std::mem::size_of::() as u64, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + //depth buffer + let depth_texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("cubes depth texture"), + size: wgpu::Extent3d { + width: target_size.width, + height: target_size.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth32Float, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }); + + let depth_view = + depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let normal_map_data = load_normal_map_data(); + + //normal map + let normal_texture = device.create_texture_with_data( + queue, + &wgpu::TextureDescriptor { + label: Some("cubes normal map texture"), + size: wgpu::Extent3d { + width: 1024, + height: 1024, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + &normal_map_data, + ); + + let normal_view = + normal_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + //skybox texture for reflection/refraction + let skybox_data = load_skybox_data(); + + let skybox_texture = device.create_texture_with_data( + queue, + &wgpu::TextureDescriptor { + label: Some("cubes skybox texture"), + size: wgpu::Extent3d { + width: SKY_TEXTURE_SIZE, + height: SKY_TEXTURE_SIZE, + depth_or_array_layers: 6, //one for each face of the cube + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + &skybox_data, + ); + + let sky_view = + skybox_texture.create_view(&wgpu::TextureViewDescriptor { + label: Some("cubes skybox texture view"), + dimension: Some(wgpu::TextureViewDimension::Cube), + ..Default::default() + }); + + let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("cubes skybox sampler"), + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + + let uniform_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("cubes uniform bind group layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { + filterable: true, + }, + view_dimension: wgpu::TextureViewDimension::Cube, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler( + wgpu::SamplerBindingType::Filtering, + ), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { + filterable: true, + }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + ], + }); + + let uniform_bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("cubes uniform bind group"), + layout: &uniform_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: uniforms.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&sky_view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Sampler(&sky_sampler), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::TextureView( + &normal_view, + ), + }, + ], + }); + + let layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("cubes pipeline layout"), + bind_group_layouts: &[&uniform_bind_group_layout], + push_constant_ranges: &[], + }); + + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("cubes shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + include_str!("../shaders/cubes.wgsl"), + )), + }); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("cubes pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::desc(), cube::Raw::desc()], + }, + primitive: wgpu::PrimitiveState::default(), + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::Max, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: None, + }); + + let depth_pipeline = DepthPipeline::new( + device, + format, + depth_texture.create_view(&wgpu::TextureViewDescriptor::default()), + ); + + Self { + pipeline, + cubes: cubes_buffer, + uniforms, + uniform_bind_group, + vertices, + depth_texture_size: target_size, + depth_view, + depth_pipeline, + } + } + + fn update_depth_texture(&mut self, device: &wgpu::Device, size: Size) { + if self.depth_texture_size.height != size.height + || self.depth_texture_size.width != size.width + { + let text = device.create_texture(&wgpu::TextureDescriptor { + label: Some("cubes depth texture"), + size: wgpu::Extent3d { + width: size.width, + height: size.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth32Float, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }); + + self.depth_view = + text.create_view(&wgpu::TextureViewDescriptor::default()); + self.depth_texture_size = size; + + self.depth_pipeline.update(device, &text); + } + } + + pub fn update( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + target_size: Size, + uniforms: &Uniforms, + num_cubes: usize, + cubes: &[cube::Raw], + ) { + //recreate depth texture if surface texture size has changed + self.update_depth_texture(device, target_size); + + // update uniforms + queue.write_buffer(&self.uniforms, 0, bytemuck::bytes_of(uniforms)); + + //resize cubes vertex buffer if cubes amount changed + let new_size = num_cubes * std::mem::size_of::(); + self.cubes.resize(device, new_size as u64); + + //always write new cube data since they are constantly rotating + queue.write_buffer(&self.cubes.raw, 0, bytemuck::cast_slice(cubes)); + } + + pub fn render( + &self, + target: &wgpu::TextureView, + encoder: &mut wgpu::CommandEncoder, + bounds: Rectangle, + num_cubes: u32, + show_depth: bool, + ) { + { + let mut pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("cubes.pipeline.pass"), + color_attachments: &[Some( + wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + }, + )], + depth_stencil_attachment: Some( + wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }, + ), + timestamp_writes: None, + occlusion_query_set: None, + }); + + pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &self.uniform_bind_group, &[]); + pass.set_vertex_buffer(0, self.vertices.slice(..)); + pass.set_vertex_buffer(1, self.cubes.raw.slice(..)); + pass.draw(0..36, 0..num_cubes); + } + + if show_depth { + self.depth_pipeline.render(encoder, target, bounds); + } + } +} + +struct DepthPipeline { + pipeline: wgpu::RenderPipeline, + bind_group_layout: wgpu::BindGroupLayout, + bind_group: wgpu::BindGroup, + sampler: wgpu::Sampler, + depth_view: wgpu::TextureView, +} + +impl DepthPipeline { + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + depth_texture: wgpu::TextureView, + ) -> Self { + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("cubes.depth_pipeline.sampler"), + ..Default::default() + }); + + let bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("cubes.depth_pipeline.bind_group_layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler( + wgpu::SamplerBindingType::NonFiltering, + ), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { + filterable: false, + }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + ], + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("cubes.depth_pipeline.bind_group"), + layout: &bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView( + &depth_texture, + ), + }, + ], + }); + + let layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("cubes.depth_pipeline.layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("cubes.depth_pipeline.shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + include_str!("../shaders/depth.wgsl"), + )), + }); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("cubes.depth_pipeline.pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + }, + primitive: wgpu::PrimitiveState::default(), + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: false, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState::default(), + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: None, + }); + + Self { + pipeline, + bind_group_layout, + bind_group, + sampler, + depth_view: depth_texture, + } + } + + pub fn update( + &mut self, + device: &wgpu::Device, + depth_texture: &wgpu::Texture, + ) { + self.depth_view = + depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + self.bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("cubes.depth_pipeline.bind_group"), + layout: &self.bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Sampler(&self.sampler), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView( + &self.depth_view, + ), + }, + ], + }); + } + + pub fn render( + &self, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + bounds: Rectangle, + ) { + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("cubes.pipeline.depth_pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: Some( + wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_view, + depth_ops: None, + stencil_ops: None, + }, + ), + timestamp_writes: None, + occlusion_query_set: None, + }); + + pass.set_scissor_rect(bounds.x, bounds.y, bounds.width, bounds.height); + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &self.bind_group, &[]); + pass.draw(0..6, 0..1); + } +} + +fn load_skybox_data() -> Vec { + let pos_x: &[u8] = include_bytes!("../textures/skybox/pos_x.jpg"); + let neg_x: &[u8] = include_bytes!("../textures/skybox/neg_x.jpg"); + let pos_y: &[u8] = include_bytes!("../textures/skybox/pos_y.jpg"); + let neg_y: &[u8] = include_bytes!("../textures/skybox/neg_y.jpg"); + let pos_z: &[u8] = include_bytes!("../textures/skybox/pos_z.jpg"); + let neg_z: &[u8] = include_bytes!("../textures/skybox/neg_z.jpg"); + + let data: [&[u8]; 6] = [pos_x, neg_x, pos_y, neg_y, pos_z, neg_z]; + + data.iter().fold(vec![], |mut acc, bytes| { + let i = image::load_from_memory_with_format( + bytes, + image::ImageFormat::Jpeg, + ) + .unwrap() + .to_rgba8() + .into_raw(); + + acc.extend(i); + acc + }) +} + +fn load_normal_map_data() -> Vec { + let bytes: &[u8] = include_bytes!("../textures/ice_cube_normal_map.png"); + + image::load_from_memory_with_format(bytes, image::ImageFormat::Png) + .unwrap() + .to_rgba8() + .into_raw() +} diff --git a/examples/custom_shader/src/scene/pipeline/buffer.rs b/examples/custom_shader/src/scene/pipeline/buffer.rs new file mode 100644 index 00000000..ef4c41c9 --- /dev/null +++ b/examples/custom_shader/src/scene/pipeline/buffer.rs @@ -0,0 +1,41 @@ +use crate::wgpu; + +// A custom buffer container for dynamic resizing. +pub struct Buffer { + pub raw: wgpu::Buffer, + label: &'static str, + size: u64, + usage: wgpu::BufferUsages, +} + +impl Buffer { + pub fn new( + device: &wgpu::Device, + label: &'static str, + size: u64, + usage: wgpu::BufferUsages, + ) -> Self { + Self { + raw: device.create_buffer(&wgpu::BufferDescriptor { + label: Some(label), + size, + usage, + mapped_at_creation: false, + }), + label, + size, + usage, + } + } + + pub fn resize(&mut self, device: &wgpu::Device, new_size: u64) { + if new_size > self.size { + self.raw = device.create_buffer(&wgpu::BufferDescriptor { + label: Some(self.label), + size: new_size, + usage: self.usage, + mapped_at_creation: false, + }); + } + } +} diff --git a/examples/custom_shader/src/scene/pipeline/cube.rs b/examples/custom_shader/src/scene/pipeline/cube.rs new file mode 100644 index 00000000..de8bad6c --- /dev/null +++ b/examples/custom_shader/src/scene/pipeline/cube.rs @@ -0,0 +1,326 @@ +use crate::scene::pipeline::Vertex; +use crate::wgpu; + +use glam::{vec2, vec3, Vec3}; +use rand::{thread_rng, Rng}; + +/// A single instance of a cube. +#[derive(Debug, Clone)] +pub struct Cube { + pub rotation: glam::Quat, + pub position: Vec3, + pub size: f32, + rotation_dir: f32, + rotation_axis: glam::Vec3, +} + +impl Default for Cube { + fn default() -> Self { + Self { + rotation: glam::Quat::IDENTITY, + position: glam::Vec3::ZERO, + size: 0.1, + rotation_dir: 1.0, + rotation_axis: glam::Vec3::Y, + } + } +} + +impl Cube { + pub fn new(size: f32, origin: Vec3) -> Self { + let rnd = thread_rng().gen_range(0.0..=1.0f32); + + Self { + rotation: glam::Quat::IDENTITY, + position: origin + Vec3::new(0.1, 0.1, 0.1), + size, + rotation_dir: if rnd <= 0.5 { -1.0 } else { 1.0 }, + rotation_axis: if rnd <= 0.33 { + glam::Vec3::Y + } else if rnd <= 0.66 { + glam::Vec3::X + } else { + glam::Vec3::Z + }, + } + } + + pub fn update(&mut self, size: f32, time: f32) { + self.rotation = glam::Quat::from_axis_angle( + self.rotation_axis, + time / 2.0 * self.rotation_dir, + ); + self.size = size; + } +} + +#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Debug)] +#[repr(C)] +pub struct Raw { + transformation: glam::Mat4, + normal: glam::Mat3, + _padding: [f32; 3], +} + +impl Raw { + const ATTRIBS: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![ + //cube transformation matrix + 4 => Float32x4, + 5 => Float32x4, + 6 => Float32x4, + 7 => Float32x4, + //normal rotation matrix + 8 => Float32x3, + 9 => Float32x3, + 10 => Float32x3, + ]; + + pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &Self::ATTRIBS, + } + } +} + +impl Raw { + pub fn from_cube(cube: &Cube) -> Raw { + Raw { + transformation: glam::Mat4::from_scale_rotation_translation( + glam::vec3(cube.size, cube.size, cube.size), + cube.rotation, + cube.position, + ), + normal: glam::Mat3::from_quat(cube.rotation), + _padding: [0.0; 3], + } + } + + pub fn vertices() -> [Vertex; 36] { + [ + //face 1 + Vertex { + pos: vec3(-0.5, -0.5, -0.5), + normal: vec3(0.0, 0.0, -1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + Vertex { + pos: vec3(0.5, -0.5, -0.5), + normal: vec3(0.0, 0.0, -1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 1.0), + }, + Vertex { + pos: vec3(0.5, 0.5, -0.5), + normal: vec3(0.0, 0.0, -1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(0.5, 0.5, -0.5), + normal: vec3(0.0, 0.0, -1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, 0.5, -0.5), + normal: vec3(0.0, 0.0, -1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, -0.5, -0.5), + normal: vec3(0.0, 0.0, -1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + //face 2 + Vertex { + pos: vec3(-0.5, -0.5, 0.5), + normal: vec3(0.0, 0.0, 1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + Vertex { + pos: vec3(0.5, -0.5, 0.5), + normal: vec3(0.0, 0.0, 1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 1.0), + }, + Vertex { + pos: vec3(0.5, 0.5, 0.5), + normal: vec3(0.0, 0.0, 1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(0.5, 0.5, 0.5), + normal: vec3(0.0, 0.0, 1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, 0.5, 0.5), + normal: vec3(0.0, 0.0, 1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, -0.5, 0.5), + normal: vec3(0.0, 0.0, 1.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + //face 3 + Vertex { + pos: vec3(-0.5, 0.5, 0.5), + normal: vec3(-1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(0.0, 1.0), + }, + Vertex { + pos: vec3(-0.5, 0.5, -0.5), + normal: vec3(-1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(1.0, 1.0), + }, + Vertex { + pos: vec3(-0.5, -0.5, -0.5), + normal: vec3(-1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, -0.5, -0.5), + normal: vec3(-1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, -0.5, 0.5), + normal: vec3(-1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(0.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, 0.5, 0.5), + normal: vec3(-1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(0.0, 1.0), + }, + //face 4 + Vertex { + pos: vec3(0.5, 0.5, 0.5), + normal: vec3(1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(0.0, 1.0), + }, + Vertex { + pos: vec3(0.5, 0.5, -0.5), + normal: vec3(1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(1.0, 1.0), + }, + Vertex { + pos: vec3(0.5, -0.5, -0.5), + normal: vec3(1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(0.5, -0.5, -0.5), + normal: vec3(1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(0.5, -0.5, 0.5), + normal: vec3(1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(0.0, 0.0), + }, + Vertex { + pos: vec3(0.5, 0.5, 0.5), + normal: vec3(1.0, 0.0, 0.0), + tangent: vec3(0.0, 0.0, -1.0), + uv: vec2(0.0, 1.0), + }, + //face 5 + Vertex { + pos: vec3(-0.5, -0.5, -0.5), + normal: vec3(0.0, -1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + Vertex { + pos: vec3(0.5, -0.5, -0.5), + normal: vec3(0.0, -1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 1.0), + }, + Vertex { + pos: vec3(0.5, -0.5, 0.5), + normal: vec3(0.0, -1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(0.5, -0.5, 0.5), + normal: vec3(0.0, -1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, -0.5, 0.5), + normal: vec3(0.0, -1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, -0.5, -0.5), + normal: vec3(0.0, -1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + //face 6 + Vertex { + pos: vec3(-0.5, 0.5, -0.5), + normal: vec3(0.0, 1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + Vertex { + pos: vec3(0.5, 0.5, -0.5), + normal: vec3(0.0, 1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 1.0), + }, + Vertex { + pos: vec3(0.5, 0.5, 0.5), + normal: vec3(0.0, 1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(0.5, 0.5, 0.5), + normal: vec3(0.0, 1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(1.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, 0.5, 0.5), + normal: vec3(0.0, 1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 0.0), + }, + Vertex { + pos: vec3(-0.5, 0.5, -0.5), + normal: vec3(0.0, 1.0, 0.0), + tangent: vec3(1.0, 0.0, 0.0), + uv: vec2(0.0, 1.0), + }, + ] + } +} diff --git a/examples/custom_shader/src/scene/pipeline/uniforms.rs b/examples/custom_shader/src/scene/pipeline/uniforms.rs new file mode 100644 index 00000000..1eac8292 --- /dev/null +++ b/examples/custom_shader/src/scene/pipeline/uniforms.rs @@ -0,0 +1,23 @@ +use crate::scene::Camera; + +use iced::{Color, Rectangle}; + +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct Uniforms { + camera_proj: glam::Mat4, + camera_pos: glam::Vec4, + light_color: glam::Vec4, +} + +impl Uniforms { + pub fn new(camera: &Camera, bounds: Rectangle, light_color: Color) -> Self { + let camera_proj = camera.build_view_proj_matrix(bounds); + + Self { + camera_proj, + camera_pos: camera.position(), + light_color: glam::Vec4::from(light_color.into_linear()), + } + } +} diff --git a/examples/custom_shader/src/scene/pipeline/vertex.rs b/examples/custom_shader/src/scene/pipeline/vertex.rs new file mode 100644 index 00000000..e64cd926 --- /dev/null +++ b/examples/custom_shader/src/scene/pipeline/vertex.rs @@ -0,0 +1,31 @@ +use crate::wgpu; + +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct Vertex { + pub pos: glam::Vec3, + pub normal: glam::Vec3, + pub tangent: glam::Vec3, + pub uv: glam::Vec2, +} + +impl Vertex { + const ATTRIBS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![ + //position + 0 => Float32x3, + //normal + 1 => Float32x3, + //tangent + 2 => Float32x3, + //uv + 3 => Float32x2, + ]; + + pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &Self::ATTRIBS, + } + } +} diff --git a/widget/src/shader.rs b/widget/src/shader.rs index fe6214db..ca140627 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -17,6 +17,7 @@ use crate::renderer::wgpu::primitive::pipeline; use std::marker::PhantomData; +pub use crate::graphics::Transformation; pub use crate::renderer::wgpu::wgpu; pub use pipeline::{Primitive, Storage}; -- cgit From 77dfa60c9640236df8b56084a6b1c58826b51e7e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 15:49:09 +0100 Subject: Move `textures` directory outside of `src` in `custom_shader` example --- examples/custom_shader/src/scene/pipeline.rs | 14 +++++++------- .../src/textures/ice_cube_normal_map.png | Bin 1773656 -> 0 bytes examples/custom_shader/src/textures/skybox/neg_x.jpg | Bin 7549 -> 0 bytes examples/custom_shader/src/textures/skybox/neg_y.jpg | Bin 2722 -> 0 bytes examples/custom_shader/src/textures/skybox/neg_z.jpg | Bin 3986 -> 0 bytes examples/custom_shader/src/textures/skybox/pos_x.jpg | Bin 5522 -> 0 bytes examples/custom_shader/src/textures/skybox/pos_y.jpg | Bin 3382 -> 0 bytes examples/custom_shader/src/textures/skybox/pos_z.jpg | Bin 5205 -> 0 bytes .../custom_shader/textures/ice_cube_normal_map.png | Bin 0 -> 1773656 bytes examples/custom_shader/textures/skybox/neg_x.jpg | Bin 0 -> 7549 bytes examples/custom_shader/textures/skybox/neg_y.jpg | Bin 0 -> 2722 bytes examples/custom_shader/textures/skybox/neg_z.jpg | Bin 0 -> 3986 bytes examples/custom_shader/textures/skybox/pos_x.jpg | Bin 0 -> 5522 bytes examples/custom_shader/textures/skybox/pos_y.jpg | Bin 0 -> 3382 bytes examples/custom_shader/textures/skybox/pos_z.jpg | Bin 0 -> 5205 bytes 15 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 examples/custom_shader/src/textures/ice_cube_normal_map.png delete mode 100644 examples/custom_shader/src/textures/skybox/neg_x.jpg delete mode 100644 examples/custom_shader/src/textures/skybox/neg_y.jpg delete mode 100644 examples/custom_shader/src/textures/skybox/neg_z.jpg delete mode 100644 examples/custom_shader/src/textures/skybox/pos_x.jpg delete mode 100644 examples/custom_shader/src/textures/skybox/pos_y.jpg delete mode 100644 examples/custom_shader/src/textures/skybox/pos_z.jpg create mode 100644 examples/custom_shader/textures/ice_cube_normal_map.png create mode 100644 examples/custom_shader/textures/skybox/neg_x.jpg create mode 100644 examples/custom_shader/textures/skybox/neg_y.jpg create mode 100644 examples/custom_shader/textures/skybox/neg_z.jpg create mode 100644 examples/custom_shader/textures/skybox/pos_x.jpg create mode 100644 examples/custom_shader/textures/skybox/pos_y.jpg create mode 100644 examples/custom_shader/textures/skybox/pos_z.jpg diff --git a/examples/custom_shader/src/scene/pipeline.rs b/examples/custom_shader/src/scene/pipeline.rs index 0967e139..3956c12e 100644 --- a/examples/custom_shader/src/scene/pipeline.rs +++ b/examples/custom_shader/src/scene/pipeline.rs @@ -582,12 +582,12 @@ impl DepthPipeline { } fn load_skybox_data() -> Vec { - let pos_x: &[u8] = include_bytes!("../textures/skybox/pos_x.jpg"); - let neg_x: &[u8] = include_bytes!("../textures/skybox/neg_x.jpg"); - let pos_y: &[u8] = include_bytes!("../textures/skybox/pos_y.jpg"); - let neg_y: &[u8] = include_bytes!("../textures/skybox/neg_y.jpg"); - let pos_z: &[u8] = include_bytes!("../textures/skybox/pos_z.jpg"); - let neg_z: &[u8] = include_bytes!("../textures/skybox/neg_z.jpg"); + let pos_x: &[u8] = include_bytes!("../../textures/skybox/pos_x.jpg"); + let neg_x: &[u8] = include_bytes!("../../textures/skybox/neg_x.jpg"); + let pos_y: &[u8] = include_bytes!("../../textures/skybox/pos_y.jpg"); + let neg_y: &[u8] = include_bytes!("../../textures/skybox/neg_y.jpg"); + let pos_z: &[u8] = include_bytes!("../../textures/skybox/pos_z.jpg"); + let neg_z: &[u8] = include_bytes!("../../textures/skybox/neg_z.jpg"); let data: [&[u8]; 6] = [pos_x, neg_x, pos_y, neg_y, pos_z, neg_z]; @@ -606,7 +606,7 @@ fn load_skybox_data() -> Vec { } fn load_normal_map_data() -> Vec { - let bytes: &[u8] = include_bytes!("../textures/ice_cube_normal_map.png"); + let bytes: &[u8] = include_bytes!("../../textures/ice_cube_normal_map.png"); image::load_from_memory_with_format(bytes, image::ImageFormat::Png) .unwrap() diff --git a/examples/custom_shader/src/textures/ice_cube_normal_map.png b/examples/custom_shader/src/textures/ice_cube_normal_map.png deleted file mode 100644 index 7b4b7228..00000000 Binary files a/examples/custom_shader/src/textures/ice_cube_normal_map.png and /dev/null differ diff --git a/examples/custom_shader/src/textures/skybox/neg_x.jpg b/examples/custom_shader/src/textures/skybox/neg_x.jpg deleted file mode 100644 index 00cc783d..00000000 Binary files a/examples/custom_shader/src/textures/skybox/neg_x.jpg and /dev/null differ diff --git a/examples/custom_shader/src/textures/skybox/neg_y.jpg b/examples/custom_shader/src/textures/skybox/neg_y.jpg deleted file mode 100644 index 548f6445..00000000 Binary files a/examples/custom_shader/src/textures/skybox/neg_y.jpg and /dev/null differ diff --git a/examples/custom_shader/src/textures/skybox/neg_z.jpg b/examples/custom_shader/src/textures/skybox/neg_z.jpg deleted file mode 100644 index 5698512e..00000000 Binary files a/examples/custom_shader/src/textures/skybox/neg_z.jpg and /dev/null differ diff --git a/examples/custom_shader/src/textures/skybox/pos_x.jpg b/examples/custom_shader/src/textures/skybox/pos_x.jpg deleted file mode 100644 index dddecba7..00000000 Binary files a/examples/custom_shader/src/textures/skybox/pos_x.jpg and /dev/null differ diff --git a/examples/custom_shader/src/textures/skybox/pos_y.jpg b/examples/custom_shader/src/textures/skybox/pos_y.jpg deleted file mode 100644 index 361427fd..00000000 Binary files a/examples/custom_shader/src/textures/skybox/pos_y.jpg and /dev/null differ diff --git a/examples/custom_shader/src/textures/skybox/pos_z.jpg b/examples/custom_shader/src/textures/skybox/pos_z.jpg deleted file mode 100644 index 0085a49e..00000000 Binary files a/examples/custom_shader/src/textures/skybox/pos_z.jpg and /dev/null differ diff --git a/examples/custom_shader/textures/ice_cube_normal_map.png b/examples/custom_shader/textures/ice_cube_normal_map.png new file mode 100644 index 00000000..7b4b7228 Binary files /dev/null and b/examples/custom_shader/textures/ice_cube_normal_map.png differ diff --git a/examples/custom_shader/textures/skybox/neg_x.jpg b/examples/custom_shader/textures/skybox/neg_x.jpg new file mode 100644 index 00000000..00cc783d Binary files /dev/null and b/examples/custom_shader/textures/skybox/neg_x.jpg differ diff --git a/examples/custom_shader/textures/skybox/neg_y.jpg b/examples/custom_shader/textures/skybox/neg_y.jpg new file mode 100644 index 00000000..548f6445 Binary files /dev/null and b/examples/custom_shader/textures/skybox/neg_y.jpg differ diff --git a/examples/custom_shader/textures/skybox/neg_z.jpg b/examples/custom_shader/textures/skybox/neg_z.jpg new file mode 100644 index 00000000..5698512e Binary files /dev/null and b/examples/custom_shader/textures/skybox/neg_z.jpg differ diff --git a/examples/custom_shader/textures/skybox/pos_x.jpg b/examples/custom_shader/textures/skybox/pos_x.jpg new file mode 100644 index 00000000..dddecba7 Binary files /dev/null and b/examples/custom_shader/textures/skybox/pos_x.jpg differ diff --git a/examples/custom_shader/textures/skybox/pos_y.jpg b/examples/custom_shader/textures/skybox/pos_y.jpg new file mode 100644 index 00000000..361427fd Binary files /dev/null and b/examples/custom_shader/textures/skybox/pos_y.jpg differ diff --git a/examples/custom_shader/textures/skybox/pos_z.jpg b/examples/custom_shader/textures/skybox/pos_z.jpg new file mode 100644 index 00000000..0085a49e Binary files /dev/null and b/examples/custom_shader/textures/skybox/pos_z.jpg differ -- cgit From 74b920a708afc2407cb495a2953e97114f7773b1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 15:52:55 +0100 Subject: Remove unnecessary `self` in `iced_style::theme` --- style/src/theme.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style/src/theme.rs b/style/src/theme.rs index cc31d72d..47010728 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -1,7 +1,7 @@ //! Use the built-in theme and styles. pub mod palette; -pub use self::palette::Palette; +pub use palette::Palette; use crate::application; use crate::button; -- cgit From 8f384c83be242f9318685530ee52dd6c27b2bb62 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 15:54:10 +0100 Subject: Remove unsused `custom.rs` file in `iced_wgpu` --- wgpu/src/custom.rs | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 wgpu/src/custom.rs diff --git a/wgpu/src/custom.rs b/wgpu/src/custom.rs deleted file mode 100644 index 98e2b396..00000000 --- a/wgpu/src/custom.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Draw custom primitives. -use crate::core::{Rectangle, Size}; -use crate::graphics::Transformation; -use crate::primitive; - -use std::any::{Any, TypeId}; -use std::collections::HashMap; -use std::fmt::Debug; -- cgit From 0968c5b64a528ff92a5a93f6586eef557546da25 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 14 Nov 2023 15:58:32 +0100 Subject: Remove unused import in `custom_shader` example --- examples/custom_shader/src/scene/pipeline.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/custom_shader/src/scene/pipeline.rs b/examples/custom_shader/src/scene/pipeline.rs index 3956c12e..94c6c562 100644 --- a/examples/custom_shader/src/scene/pipeline.rs +++ b/examples/custom_shader/src/scene/pipeline.rs @@ -4,7 +4,6 @@ mod buffer; mod uniforms; mod vertex; -pub use cube::Cube; pub use uniforms::Uniforms; use buffer::Buffer; -- cgit From 7dd32f3be43c72e11dac5e07918e9ad6d36b6555 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 15 Nov 2023 10:27:26 +0100 Subject: Update `itertools` dependency for `game_of_life` example --- examples/game_of_life/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml index 9b291de8..7596844c 100644 --- a/examples/game_of_life/Cargo.toml +++ b/examples/game_of_life/Cargo.toml @@ -9,7 +9,7 @@ publish = false iced.workspace = true iced.features = ["debug", "canvas", "tokio"] -itertools = "0.11" +itertools = "0.12" rustc-hash.workspace = true tokio = { workspace = true, features = ["sync"] } tracing-subscriber = "0.3" -- cgit From 25006b9c6f2ae909d86871d3a13631d518c07158 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 21 Nov 2023 14:41:22 +0100 Subject: Fix `Overlay` composition Translations were not easily composable. --- core/src/overlay.rs | 3 ++- core/src/overlay/element.rs | 20 ++++++++++++++++---- core/src/overlay/group.rs | 9 +++++---- runtime/src/overlay/nested.rs | 18 +++++++++++++----- runtime/src/user_interface.rs | 30 +++++++++++++++++++++++------- widget/src/lazy.rs | 5 +++-- widget/src/lazy/component.rs | 3 ++- widget/src/lazy/responsive.rs | 6 ++++-- widget/src/overlay/menu.rs | 1 + widget/src/tooltip.rs | 1 + 10 files changed, 70 insertions(+), 26 deletions(-) diff --git a/core/src/overlay.rs b/core/src/overlay.rs index f71f25f7..af10afee 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -11,7 +11,7 @@ use crate::mouse; use crate::renderer; use crate::widget; use crate::widget::Tree; -use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size}; +use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector}; /// An interactive component that can be displayed on top of other widgets. pub trait Overlay @@ -29,6 +29,7 @@ where renderer: &Renderer, bounds: Size, position: Point, + translation: Vector, ) -> layout::Node; /// Draws the [`Overlay`] using the associated `Renderer`. diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index 3dd58f9b..a279fe28 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -13,6 +13,7 @@ use std::any::Any; #[allow(missing_debug_implementations)] pub struct Element<'a, Message, Renderer> { position: Point, + translation: Vector, overlay: Box + 'a>, } @@ -25,7 +26,11 @@ where position: Point, overlay: Box + 'a>, ) -> Self { - Self { position, overlay } + Self { + position, + overlay, + translation: Vector::ZERO, + } } /// Returns the position of the [`Element`]. @@ -36,6 +41,7 @@ where /// Translates the [`Element`]. pub fn translate(mut self, translation: Vector) -> Self { self.position = self.position + translation; + self.translation = self.translation + translation; self } @@ -48,6 +54,7 @@ where { Element { position: self.position, + translation: self.translation, overlay: Box::new(Map::new(self.overlay, f)), } } @@ -59,8 +66,12 @@ where bounds: Size, translation: Vector, ) -> layout::Node { - self.overlay - .layout(renderer, bounds, self.position + translation) + self.overlay.layout( + renderer, + bounds, + self.position + translation, + self.translation + translation, + ) } /// Processes a runtime [`Event`]. @@ -154,8 +165,9 @@ where renderer: &Renderer, bounds: Size, position: Point, + translation: Vector, ) -> layout::Node { - self.content.layout(renderer, bounds, position) + self.content.layout(renderer, bounds, position, translation) } fn operate( diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index dccf6dba..e1e9727a 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -4,7 +4,9 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::widget; -use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size}; +use crate::{ + Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size, Vector, +}; /// An [`Overlay`] container that displays multiple overlay [`overlay::Element`] /// children. @@ -64,10 +66,9 @@ where &mut self, renderer: &Renderer, bounds: Size, - position: Point, + _position: Point, + translation: Vector, ) -> layout::Node { - let translation = position - Point::ORIGIN; - layout::Node::with_children( bounds, self.children diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index 062ccc72..b7cfc918 100644 --- a/runtime/src/overlay/nested.rs +++ b/runtime/src/overlay/nested.rs @@ -4,7 +4,9 @@ use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::widget; -use crate::core::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size}; +use crate::core::{ + Clipboard, Event, Layout, Point, Rectangle, Shell, Size, Vector, +}; /// An overlay container that displays nested overlays #[allow(missing_debug_implementations)] @@ -34,18 +36,18 @@ where renderer: &Renderer, bounds: Size, position: Point, + translation: Vector, ) -> layout::Node { fn recurse( element: &mut overlay::Element<'_, Message, Renderer>, renderer: &Renderer, bounds: Size, position: Point, + translation: Vector, ) -> layout::Node where Renderer: renderer::Renderer, { - let translation = position - Point::ORIGIN; - let node = element.layout(renderer, bounds, translation); if let Some(mut nested) = @@ -55,7 +57,13 @@ where node.size(), vec![ node, - recurse(&mut nested, renderer, bounds, position), + recurse( + &mut nested, + renderer, + bounds, + position, + translation, + ), ], ) } else { @@ -63,7 +71,7 @@ where } } - recurse(&mut self.overlay, renderer, bounds, position) + recurse(&mut self.overlay, renderer, bounds, position, translation) } /// Draws the [`Nested`] overlay using the associated `Renderer`. diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index dae9e0ac..3594ac18 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -5,7 +5,9 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::widget; use crate::core::window; -use crate::core::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; +use crate::core::{ + Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector, +}; use crate::overlay; /// A set of interactive graphical elements with a specific [`Layout`]. @@ -199,7 +201,8 @@ where let bounds = self.bounds; let mut overlay = manual_overlay.as_mut().unwrap(); - let mut layout = overlay.layout(renderer, bounds, Point::ORIGIN); + let mut layout = + overlay.layout(renderer, bounds, Point::ORIGIN, Vector::ZERO); let mut event_statuses = Vec::new(); for event in events.iter().cloned() { @@ -253,8 +256,12 @@ where overlay = manual_overlay.as_mut().unwrap(); shell.revalidate_layout(|| { - layout = - overlay.layout(renderer, bounds, Point::ORIGIN); + layout = overlay.layout( + renderer, + bounds, + Point::ORIGIN, + Vector::ZERO, + ); }); } @@ -448,7 +455,12 @@ where .map(overlay::Nested::new) { let overlay_layout = self.overlay.take().unwrap_or_else(|| { - overlay.layout(renderer, self.bounds, Point::ORIGIN) + overlay.layout( + renderer, + self.bounds, + Point::ORIGIN, + Vector::ZERO, + ) }); let cursor = if cursor @@ -566,8 +578,12 @@ where .map(overlay::Nested::new) { if self.overlay.is_none() { - self.overlay = - Some(overlay.layout(renderer, self.bounds, Point::ORIGIN)); + self.overlay = Some(overlay.layout( + renderer, + self.bounds, + Point::ORIGIN, + Vector::ZERO, + )); } overlay.operate( diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 589dd938..167a055d 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -18,7 +18,7 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::widget::{self, Widget}; use crate::core::Element; use crate::core::{ - self, Clipboard, Hasher, Length, Point, Rectangle, Shell, Size, + self, Clipboard, Hasher, Length, Point, Rectangle, Shell, Size, Vector, }; use crate::runtime::overlay::Nested; @@ -333,9 +333,10 @@ where renderer: &Renderer, bounds: Size, position: Point, + translation: Vector, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - overlay.layout(renderer, bounds, position) + overlay.layout(renderer, bounds, position, translation) }) .unwrap_or_default() } diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index d454b72b..ad0c3823 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -577,9 +577,10 @@ where renderer: &Renderer, bounds: Size, position: Point, + translation: Vector, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - overlay.layout(renderer, bounds, position) + overlay.layout(renderer, bounds, position, translation) }) .unwrap_or_default() } diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index ed471988..86d37b6c 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -6,7 +6,8 @@ use crate::core::renderer; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, + self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, + Widget, }; use crate::horizontal_space; use crate::runtime::overlay::Nested; @@ -367,9 +368,10 @@ where renderer: &Renderer, bounds: Size, position: Point, + translation: Vector, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - overlay.layout(renderer, bounds, position) + overlay.layout(renderer, bounds, position, translation) }) .unwrap_or_default() } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index b293f9fa..5098fa17 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -236,6 +236,7 @@ where renderer: &Renderer, bounds: Size, position: Point, + _translation: Vector, ) -> layout::Node { let space_below = bounds.height - (position.y + self.target_height); let space_above = position.y; diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index b041d2e9..d5ee3de2 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -325,6 +325,7 @@ where renderer: &Renderer, bounds: Size, position: Point, + _translation: Vector, ) -> layout::Node { let viewport = Rectangle::with_size(bounds); -- cgit From 921ddec1285027a6a2feb88b0a5c29ec8f942f8b Mon Sep 17 00:00:00 2001 From: arslee07 Date: Wed, 22 Nov 2023 00:32:01 +0900 Subject: Use the correct GIF for the progress bar example --- examples/progress_bar/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/progress_bar/README.md b/examples/progress_bar/README.md index 1268ac6b..a87829c6 100644 --- a/examples/progress_bar/README.md +++ b/examples/progress_bar/README.md @@ -5,7 +5,7 @@ A simple progress bar that can be filled by using a slider. The __[`main`]__ file contains all the code of the example.

You can run it with `cargo run`: -- cgit From a1439071d691be8096ce956df90d1553fe5b3694 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 21 Nov 2023 18:53:31 +0100 Subject: Remove unused `position` argument in `overlay::Nested` --- runtime/src/overlay/nested.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index b7cfc918..4256efb7 100644 --- a/runtime/src/overlay/nested.rs +++ b/runtime/src/overlay/nested.rs @@ -35,14 +35,13 @@ where &mut self, renderer: &Renderer, bounds: Size, - position: Point, + _position: Point, translation: Vector, ) -> layout::Node { fn recurse( element: &mut overlay::Element<'_, Message, Renderer>, renderer: &Renderer, bounds: Size, - position: Point, translation: Vector, ) -> layout::Node where @@ -57,13 +56,7 @@ where node.size(), vec![ node, - recurse( - &mut nested, - renderer, - bounds, - position, - translation, - ), + recurse(&mut nested, renderer, bounds, translation), ], ) } else { @@ -71,7 +64,7 @@ where } } - recurse(&mut self.overlay, renderer, bounds, position, translation) + recurse(&mut self.overlay, renderer, bounds, translation) } /// Draws the [`Nested`] overlay using the associated `Renderer`. -- cgit From 89e3de7c08dc07eefbcc2617f0da63282aa1c8ef Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 21 Nov 2023 18:55:53 +0100 Subject: Fix `modal` and `toast` examples --- examples/modal/src/main.rs | 2 ++ examples/toast/src/main.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index 3b69f5e6..acb14372 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -231,6 +231,7 @@ mod modal { use iced::mouse; use iced::{ BorderRadius, Color, Element, Event, Length, Point, Rectangle, Size, + Vector, }; /// A widget that centers a modal element over some base element @@ -413,6 +414,7 @@ mod modal { renderer: &Renderer, _bounds: Size, position: Point, + _translation: Vector, ) -> layout::Node { let limits = layout::Limits::new(Size::ZERO, self.size) .width(Length::Fill) diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 5b089e8a..934049d5 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -511,6 +511,7 @@ mod toast { renderer: &Renderer, bounds: Size, position: Point, + _translation: Vector, ) -> layout::Node { let limits = layout::Limits::new(Size::ZERO, bounds) .width(Length::Fill) -- cgit From f67387f2d80e744618a5f4f107509ba24802146c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 21 Nov 2023 18:11:31 +0100 Subject: Invalidate layout when `Tooltip` changes `overlay` --- widget/src/tooltip.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index b041d2e9..9745bc1c 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -157,11 +157,19 @@ where ) -> event::Status { let state = tree.state.downcast_mut::(); + let was_idle = *state == State::Idle; + *state = cursor .position_over(layout.bounds()) .map(|cursor_position| State::Hovered { cursor_position }) .unwrap_or_default(); + let is_idle = *state == State::Idle; + + if was_idle != is_idle { + shell.invalidate_layout(); + } + self.content.as_widget_mut().on_event( &mut tree.children[0], event, @@ -289,7 +297,7 @@ pub enum Position { Right, } -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Default)] enum State { #[default] Idle, -- cgit From ab7dae554cac801aeed5d9aa4d3850d50be86263 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 28 Nov 2023 23:13:38 +0100 Subject: Provide actual bounds to `Shader` primitives ... and allow for proper translation and scissoring. --- examples/custom_shader/src/main.rs | 21 ++++++++------------- examples/custom_shader/src/scene.rs | 6 +++--- examples/custom_shader/src/scene/pipeline.rs | 21 +++++++++++++-------- wgpu/src/backend.rs | 8 ++++---- wgpu/src/layer.rs | 15 +++++++-------- wgpu/src/layer/pipeline.rs | 17 +++++++++++++++++ wgpu/src/primitive/pipeline.rs | 5 ++--- widget/src/shader.rs | 1 - 8 files changed, 54 insertions(+), 40 deletions(-) create mode 100644 wgpu/src/layer/pipeline.rs diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index 2eb1ac4a..3bfa3a43 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -5,9 +5,7 @@ use scene::Scene; use iced::executor; use iced::time::Instant; use iced::widget::shader::wgpu; -use iced::widget::{ - checkbox, column, container, row, shader, slider, text, vertical_space, -}; +use iced::widget::{checkbox, column, container, row, shader, slider, text}; use iced::window; use iced::{ Alignment, Application, Color, Command, Element, Length, Renderer, @@ -138,21 +136,18 @@ impl Application for IcedCubes { let controls = column![top_controls, bottom_controls,] .spacing(10) + .padding(20) .align_items(Alignment::Center); let shader = shader(&self.scene).width(Length::Fill).height(Length::Fill); - container( - column![shader, controls, vertical_space(20),] - .spacing(40) - .align_items(Alignment::Center), - ) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() + container(column![shader, controls].align_items(Alignment::Center)) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() } fn subscription(&self) -> Subscription { diff --git a/examples/custom_shader/src/scene.rs b/examples/custom_shader/src/scene.rs index 3b291ce2..a35efdd9 100644 --- a/examples/custom_shader/src/scene.rs +++ b/examples/custom_shader/src/scene.rs @@ -133,9 +133,9 @@ impl shader::Primitive for Primitive { format: wgpu::TextureFormat, device: &wgpu::Device, queue: &wgpu::Queue, + _bounds: Rectangle, target_size: Size, _scale_factor: f32, - _transform: shader::Transformation, storage: &mut shader::Storage, ) { if !storage.has::() { @@ -158,9 +158,9 @@ impl shader::Primitive for Primitive { fn render( &self, storage: &shader::Storage, - bounds: Rectangle, target: &wgpu::TextureView, _target_size: Size, + viewport: Rectangle, encoder: &mut wgpu::CommandEncoder, ) { //at this point our pipeline should always be initialized @@ -170,7 +170,7 @@ impl shader::Primitive for Primitive { pipeline.render( target, encoder, - bounds, + viewport, self.cubes.len() as u32, self.show_depth_buffer, ); diff --git a/examples/custom_shader/src/scene/pipeline.rs b/examples/custom_shader/src/scene/pipeline.rs index 94c6c562..124b421f 100644 --- a/examples/custom_shader/src/scene/pipeline.rs +++ b/examples/custom_shader/src/scene/pipeline.rs @@ -351,7 +351,7 @@ impl Pipeline { &self, target: &wgpu::TextureView, encoder: &mut wgpu::CommandEncoder, - bounds: Rectangle, + viewport: Rectangle, num_cubes: u32, show_depth: bool, ) { @@ -384,10 +384,10 @@ impl Pipeline { }); pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, + viewport.x, + viewport.y, + viewport.width, + viewport.height, ); pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, &self.uniform_bind_group, &[]); @@ -397,7 +397,7 @@ impl Pipeline { } if show_depth { - self.depth_pipeline.render(encoder, target, bounds); + self.depth_pipeline.render(encoder, target, viewport); } } } @@ -550,7 +550,7 @@ impl DepthPipeline { &self, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, - bounds: Rectangle, + viewport: Rectangle, ) { let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("cubes.pipeline.depth_pass"), @@ -573,7 +573,12 @@ impl DepthPipeline { occlusion_query_set: None, }); - pass.set_scissor_rect(bounds.x, bounds.y, bounds.width, bounds.height); + pass.set_scissor_rect( + viewport.x, + viewport.y, + viewport.width, + viewport.height, + ); pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, &self.bind_group, &[]); pass.draw(0..6, 0..1); diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 88caad06..25134d68 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -192,9 +192,9 @@ impl Backend { format, device, queue, + pipeline.bounds, target_size, scale_factor, - transformation, &mut self.pipeline_storage, ); } @@ -327,17 +327,17 @@ impl Backend { let _ = ManuallyDrop::into_inner(render_pass); for pipeline in &layer.pipelines { - let bounds = (pipeline.bounds * scale_factor).snap(); + let viewport = (pipeline.viewport * scale_factor).snap(); - if bounds.width < 1 || bounds.height < 1 { + if viewport.width < 1 || viewport.height < 1 { continue; } pipeline.primitive.render( &self.pipeline_storage, - bounds, target, target_size, + viewport, encoder, ); } diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 33aaf670..98e49f1a 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -1,11 +1,13 @@ //! Organize rendering primitives into a flattened list of layers. mod image; +mod pipeline; mod text; pub mod mesh; pub use image::Image; pub use mesh::Mesh; +pub use pipeline::Pipeline; pub use text::Text; use crate::core; @@ -36,7 +38,7 @@ pub struct Layer<'a> { pub images: Vec, /// The custom pipelines of this [`Layer`]. - pub pipelines: Vec, + pub pipelines: Vec, } impl<'a> Layer<'a> { @@ -314,17 +316,14 @@ impl<'a> Layer<'a> { }, primitive::Custom::Pipeline(pipeline) => { let layer = &mut layers[current_layer]; - - let bounds = Rectangle::new( - Point::new(translation.x, translation.y), - pipeline.bounds.size(), - ); + let bounds = pipeline.bounds + translation; if let Some(clip_bounds) = layer.bounds.intersection(&bounds) { - layer.pipelines.push(primitive::Pipeline { - bounds: clip_bounds, + layer.pipelines.push(Pipeline { + bounds, + viewport: clip_bounds, primitive: pipeline.primitive.clone(), }); } diff --git a/wgpu/src/layer/pipeline.rs b/wgpu/src/layer/pipeline.rs new file mode 100644 index 00000000..6dfe6750 --- /dev/null +++ b/wgpu/src/layer/pipeline.rs @@ -0,0 +1,17 @@ +use crate::core::Rectangle; +use crate::primitive::pipeline::Primitive; + +use std::sync::Arc; + +#[derive(Clone, Debug)] +/// A custom primitive which can be used to render primitives associated with a custom pipeline. +pub struct Pipeline { + /// The bounds of the [`Pipeline`]. + pub bounds: Rectangle, + + /// The viewport of the [`Pipeline`]. + pub viewport: Rectangle, + + /// The [`Primitive`] to render. + pub primitive: Arc, +} diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs index 5dbd6697..302e38f6 100644 --- a/wgpu/src/primitive/pipeline.rs +++ b/wgpu/src/primitive/pipeline.rs @@ -1,6 +1,5 @@ //! Draw primitives using custom pipelines. use crate::core::{Rectangle, Size}; -use crate::graphics::Transformation; use std::any::{Any, TypeId}; use std::collections::HashMap; @@ -41,9 +40,9 @@ pub trait Primitive: Debug + Send + Sync + 'static { format: wgpu::TextureFormat, device: &wgpu::Device, queue: &wgpu::Queue, + bounds: Rectangle, target_size: Size, scale_factor: f32, - transform: Transformation, storage: &mut Storage, ); @@ -51,9 +50,9 @@ pub trait Primitive: Debug + Send + Sync + 'static { fn render( &self, storage: &Storage, - bounds: Rectangle, target: &wgpu::TextureView, target_size: Size, + viewport: Rectangle, encoder: &mut wgpu::CommandEncoder, ); } diff --git a/widget/src/shader.rs b/widget/src/shader.rs index ca140627..fe6214db 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -17,7 +17,6 @@ use crate::renderer::wgpu::primitive::pipeline; use std::marker::PhantomData; -pub use crate::graphics::Transformation; pub use crate::renderer::wgpu::wgpu; pub use pipeline::{Primitive, Storage}; -- cgit From 3b7d479534d9114ed12bb5d9ccd910e85d5c13c7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 29 Nov 2023 00:12:48 +0100 Subject: Implement `Command::run` for executing a `Stream` to completion --- futures/src/runtime.rs | 25 ++++++++++++++++++++++++- runtime/src/command.rs | 14 +++++++++++++- runtime/src/command/action.rs | 9 ++++++++- winit/src/application.rs | 3 +++ 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/futures/src/runtime.rs b/futures/src/runtime.rs index 16111b36..cac7b7e1 100644 --- a/futures/src/runtime.rs +++ b/futures/src/runtime.rs @@ -1,7 +1,7 @@ //! Run commands and keep track of subscriptions. use crate::core::event::{self, Event}; use crate::subscription; -use crate::{BoxFuture, Executor, MaybeSend}; +use crate::{BoxFuture, BoxStream, Executor, MaybeSend}; use futures::{channel::mpsc, Sink}; use std::marker::PhantomData; @@ -69,6 +69,29 @@ where self.executor.spawn(future); } + /// Runs a [`Stream`] in the [`Runtime`] until completion. + /// + /// The resulting `Message`s will be forwarded to the `Sender` of the + /// [`Runtime`]. + /// + /// [`Stream`]: BoxStream + pub fn run(&mut self, stream: BoxStream) { + use futures::{FutureExt, StreamExt}; + + let sender = self.sender.clone(); + let future = + stream.map(Ok).forward(sender).map(|result| match result { + Ok(()) => (), + Err(error) => { + log::warn!( + "Stream could not run until completion: {error}" + ); + } + }); + + self.executor.spawn(future); + } + /// Tracks a [`Subscription`] in the [`Runtime`]. /// /// It will spawn new streams or close old ones as necessary! See diff --git a/runtime/src/command.rs b/runtime/src/command.rs index b74097bd..b942f3ce 100644 --- a/runtime/src/command.rs +++ b/runtime/src/command.rs @@ -4,8 +4,10 @@ mod action; pub use action::Action; use crate::core::widget; +use crate::futures::futures; use crate::futures::MaybeSend; +use futures::Stream; use std::fmt; use std::future::Future; @@ -43,11 +45,21 @@ impl Command { future: impl Future + 'static + MaybeSend, f: impl FnOnce(A) -> T + 'static + MaybeSend, ) -> Command { - use iced_futures::futures::FutureExt; + use futures::FutureExt; Command::single(Action::Future(Box::pin(future.map(f)))) } + /// Creates a [`Command`] that runs the given stream to completion. + pub fn run( + stream: impl Stream + 'static + MaybeSend, + f: impl Fn(A) -> T + 'static + MaybeSend, + ) -> Command { + use futures::StreamExt; + + Command::single(Action::Stream(Box::pin(stream.map(f)))) + } + /// Creates a [`Command`] that performs the actions of all the given /// commands. /// diff --git a/runtime/src/command/action.rs b/runtime/src/command/action.rs index 6c74f0ef..6551e233 100644 --- a/runtime/src/command/action.rs +++ b/runtime/src/command/action.rs @@ -18,6 +18,11 @@ pub enum Action { /// [`Future`]: iced_futures::BoxFuture Future(iced_futures::BoxFuture), + /// Run a [`Stream`] to completion. + /// + /// [`Stream`]: iced_futures::BoxStream + Stream(iced_futures::BoxStream), + /// Run a clipboard action. Clipboard(clipboard::Action), @@ -52,10 +57,11 @@ impl Action { A: 'static, T: 'static, { - use iced_futures::futures::FutureExt; + use iced_futures::futures::{FutureExt, StreamExt}; match self { Self::Future(future) => Action::Future(Box::pin(future.map(f))), + Self::Stream(stream) => Action::Stream(Box::pin(stream.map(f))), Self::Clipboard(action) => Action::Clipboard(action.map(f)), Self::Window(window) => Action::Window(window.map(f)), Self::System(system) => Action::System(system.map(f)), @@ -74,6 +80,7 @@ impl fmt::Debug for Action { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Future(_) => write!(f, "Action::Future"), + Self::Stream(_) => write!(f, "Action::Stream"), Self::Clipboard(action) => { write!(f, "Action::Clipboard({action:?})") } diff --git a/winit/src/application.rs b/winit/src/application.rs index 315e34d9..2c5c864a 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -736,6 +736,9 @@ pub fn run_command( command::Action::Future(future) => { runtime.spawn(future); } + command::Action::Stream(stream) => { + runtime.run(stream); + } command::Action::Clipboard(action) => match action { clipboard::Action::Read(tag) => { let message = tag(clipboard.read()); -- cgit From a761448858521d11dc646e2ef5217e9e06628932 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 29 Nov 2023 00:14:27 +0100 Subject: Implement `command::channel` helper It is analogous to `subscription::channel`. --- runtime/src/command.rs | 21 +++++++++++++++++++++ src/lib.rs | 7 ++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/runtime/src/command.rs b/runtime/src/command.rs index b942f3ce..f70da915 100644 --- a/runtime/src/command.rs +++ b/runtime/src/command.rs @@ -7,6 +7,7 @@ use crate::core::widget; use crate::futures::futures; use crate::futures::MaybeSend; +use futures::channel::mpsc; use futures::Stream; use std::fmt; use std::future::Future; @@ -118,3 +119,23 @@ impl fmt::Debug for Command { command.fmt(f) } } + +/// Creates a [`Command`] that produces the `Message`s published from a [`Future`] +/// to an [`mpsc::Sender`] with the given bounds. +pub fn channel( + size: usize, + f: impl FnOnce(mpsc::Sender) -> Fut + MaybeSend + 'static, +) -> Command +where + Fut: Future + MaybeSend + 'static, + Message: 'static + MaybeSend, +{ + use futures::future; + use futures::stream::{self, StreamExt}; + + let (sender, receiver) = mpsc::channel(size); + + let runner = stream::once(f(sender)).filter_map(|_| future::ready(None)); + + Command::single(Action::Stream(Box::pin(stream::select(receiver, runner)))) +} diff --git a/src/lib.rs b/src/lib.rs index f9f3952c..47766e6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,7 +190,6 @@ pub use crate::core::{ color, Alignment, Background, BorderRadius, Color, ContentFit, Degrees, Gradient, Length, Padding, Pixels, Point, Radians, Rectangle, Size, Vector, }; -pub use crate::runtime::Command; pub mod clipboard { //! Access the clipboard. @@ -239,6 +238,11 @@ pub mod mouse { }; } +pub mod command { + //! Run asynchronous actions. + pub use crate::runtime::command::{channel, Command}; +} + pub mod subscription { //! Listen to external events in your application. pub use iced_futures::subscription::{ @@ -287,6 +291,7 @@ pub mod widget { } pub use application::Application; +pub use command::Command; pub use error::Error; pub use event::Event; pub use executor::Executor; -- cgit From 7e7d65a23d864e4662c43028a41244ca30cac540 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 29 Nov 2023 01:06:58 +0100 Subject: Fix Discourse badge in `README` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 825219aa..eb2befbc 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![License](https://img.shields.io/crates/l/iced.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE) [![Downloads](https://img.shields.io/crates/d/iced.svg)](https://crates.io/crates/iced) [![Test Status](https://img.shields.io/github/actions/workflow/status/iced-rs/iced/test.yml?branch=master&event=push&label=test)](https://github.com/iced-rs/iced/actions) -[![Discourse](https://img.shields.io/discourse/users?server=https%3A%2F%2Fdiscourse.iced.rs&color=5e7ce2)](https://discourse.iced.rs/) +[![Discourse](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdiscourse.iced.rs%2Fsite%2Fstatistics.json&query=%24.users_count&suffix=%20users&label=discourse&color=5e7ce2)](https://discourse.iced.rs/) [![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd) A cross-platform GUI library for Rust focused on simplicity and type-safety. -- cgit