diff options
Diffstat (limited to 'widget')
38 files changed, 970 insertions, 404 deletions
diff --git a/widget/src/action.rs b/widget/src/action.rs index 1dd3a787..cc31e76a 100644 --- a/widget/src/action.rs +++ b/widget/src/action.rs @@ -6,7 +6,7 @@ use crate::core::window; #[derive(Debug, Clone)] pub struct Action<Message> { message_to_publish: Option<Message>, - redraw_request: Option<window::RedrawRequest>, + redraw_request: window::RedrawRequest, event_status: event::Status, } @@ -14,7 +14,7 @@ impl<Message> Action<Message> { fn new() -> Self { Self { message_to_publish: None, - redraw_request: None, + redraw_request: window::RedrawRequest::Wait, event_status: event::Status::Ignored, } } @@ -46,7 +46,7 @@ impl<Message> Action<Message> { /// soon as possible; without publishing any `Message`. pub fn request_redraw() -> Self { Self { - redraw_request: Some(window::RedrawRequest::NextFrame), + redraw_request: window::RedrawRequest::NextFrame, ..Self::new() } } @@ -58,7 +58,7 @@ impl<Message> Action<Message> { /// blinking caret on a text input. pub fn request_redraw_at(at: Instant) -> Self { Self { - redraw_request: Some(window::RedrawRequest::At(at)), + redraw_request: window::RedrawRequest::At(at), ..Self::new() } } @@ -75,11 +75,7 @@ impl<Message> Action<Message> { /// widget implementations. pub fn into_inner( self, - ) -> ( - Option<Message>, - Option<window::RedrawRequest>, - event::Status, - ) { + ) -> (Option<Message>, window::RedrawRequest, event::Status) { ( self.message_to_publish, self.redraw_request, diff --git a/widget/src/button.rs b/widget/src/button.rs index 11839d5e..0e24328f 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -275,7 +275,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -285,7 +285,7 @@ where ) { self.content.as_widget_mut().update( &mut tree.children[0], - event.clone(), + event, layout.children().next().unwrap(), cursor, renderer, diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 23cc3f2b..046abddf 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -218,7 +218,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -238,27 +238,18 @@ where { let (message, redraw_request, event_status) = action.into_inner(); + shell.request_redraw_at(redraw_request); + if let Some(message) = message { shell.publish(message); } - if let Some(redraw_request) = redraw_request { - match redraw_request { - window::RedrawRequest::NextFrame => { - shell.request_redraw(); - } - window::RedrawRequest::At(at) => { - shell.request_redraw_at(at); - } - } - } - if event_status == event::Status::Captured { shell.capture_event(); } } - if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) { + if shell.redraw_request() != window::RedrawRequest::NextFrame { let mouse_interaction = self .mouse_interaction(tree, layout, cursor, viewport, renderer); diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs index c68b2830..43446b64 100644 --- a/widget/src/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -32,7 +32,7 @@ where fn update( &self, _state: &mut Self::State, - _event: Event, + _event: &Event, _bounds: Rectangle, _cursor: mouse::Cursor, ) -> Option<Action<Message>> { @@ -82,7 +82,7 @@ where fn update( &self, state: &mut Self::State, - event: Event, + event: &Event, bounds: Rectangle, cursor: mouse::Cursor, ) -> Option<Action<Message>> { diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 663bfad1..6ed3e080 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -305,7 +305,7 @@ where fn update( &mut self, _tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/widget/src/column.rs b/widget/src/column.rs index c729cbdb..7200690b 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -260,7 +260,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -275,13 +275,7 @@ where .zip(layout.children()) { child.as_widget_mut().update( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, + state, event, layout, cursor, renderer, clipboard, shell, viewport, ); } diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 500d2bec..f71e4a6e 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -63,7 +63,6 @@ use crate::core::renderer; use crate::core::text; use crate::core::time::Instant; use crate::core::widget::{self, Widget}; -use crate::core::window; use crate::core::{ Clipboard, Element, Event, Length, Padding, Rectangle, Shell, Size, Theme, Vector, @@ -513,7 +512,7 @@ where fn update( &mut self, tree: &mut widget::Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -541,7 +540,7 @@ where // Provide it to the widget self.text_input.update( &mut tree.children[0], - event.clone(), + event, layout, cursor, renderer, @@ -554,16 +553,8 @@ where shell.capture_event(); } - if let Some(redraw_request) = local_shell.redraw_request() { - match redraw_request { - window::RedrawRequest::NextFrame => { - shell.request_redraw(); - } - window::RedrawRequest::At(at) => { - shell.request_redraw_at(at); - } - } - } + shell.request_redraw_at(local_shell.redraw_request()); + shell.request_input_method(local_shell.input_method()); // Then finally react to them here for message in local_messages { @@ -742,18 +733,21 @@ where published_message_to_shell = true; // Unfocus the input + let mut local_messages = Vec::new(); + let mut local_shell = Shell::new(&mut local_messages); self.text_input.update( &mut tree.children[0], - Event::Mouse(mouse::Event::ButtonPressed( + &Event::Mouse(mouse::Event::ButtonPressed( mouse::Button::Left, )), layout, mouse::Cursor::Unavailable, renderer, clipboard, - &mut Shell::new(&mut vec![]), + &mut local_shell, viewport, ); + shell.request_input_method(local_shell.input_method()); } }); diff --git a/widget/src/container.rs b/widget/src/container.rs index 852481f1..86c1c7a8 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -26,6 +26,7 @@ use crate::core::layout; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; +use crate::core::theme; use crate::core::widget::tree::{self, Tree}; use crate::core::widget::{self, Operation}; use crate::core::{ @@ -300,7 +301,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -714,9 +715,44 @@ pub fn bordered_box(theme: &Theme) -> Style { /// A [`Container`] with a dark background and white text. pub fn dark(_theme: &Theme) -> Style { + style(theme::palette::Pair { + color: color!(0x111111), + text: Color::WHITE, + }) +} + +/// A [`Container`] with a primary background color. +pub fn primary(theme: &Theme) -> Style { + let palette = theme.extended_palette(); + + style(palette.primary.base) +} + +/// A [`Container`] with a secondary background color. +pub fn secondary(theme: &Theme) -> Style { + let palette = theme.extended_palette(); + + style(palette.secondary.base) +} + +/// A [`Container`] with a success background color. +pub fn success(theme: &Theme) -> Style { + let palette = theme.extended_palette(); + + style(palette.success.base) +} + +/// A [`Container`] with a danger background color. +pub fn danger(theme: &Theme) -> Style { + let palette = theme.extended_palette(); + + style(palette.danger.base) +} + +fn style(pair: theme::palette::Pair) -> Style { Style { - background: Some(color!(0x111111).into()), - text_color: Some(Color::WHITE), + background: Some(pair.color.into()), + text_color: Some(pair.text), border: border::rounded(2), ..Style::default() } diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 17cf94cc..42d0f499 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -167,7 +167,7 @@ macro_rules! text { /// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; /// use iced::font; /// use iced::widget::{rich_text, span}; -/// use iced::{color, Font}; +/// use iced::{color, never, Font}; /// /// #[derive(Debug, Clone)] /// enum Message { @@ -177,9 +177,10 @@ macro_rules! text { /// fn view(state: &State) -> Element<'_, Message> { /// rich_text![ /// span("I am red!").color(color!(0xff0000)), -/// " ", +/// span(" "), /// span("And I am bold!").font(Font { weight: font::Weight::Bold, ..Font::default() }), /// ] +/// .on_link_click(never) /// .size(20) /// .into() /// } @@ -187,7 +188,7 @@ macro_rules! text { #[macro_export] macro_rules! rich_text { () => ( - $crate::Column::new() + $crate::text::Rich::new() ); ($($x:expr),+ $(,)?) => ( $crate::text::Rich::from_iter([$($crate::text::Span::from($x)),+]) @@ -633,7 +634,7 @@ where fn update( &mut self, state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -836,7 +837,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -871,26 +872,28 @@ where shell.request_redraw(); } + let is_visible = + is_hovered || self.is_top_focused || self.is_top_overlay_active; + if matches!( event, Event::Mouse( mouse::Event::CursorMoved { .. } | mouse::Event::ButtonReleased(_) ) - ) || is_hovered - || self.is_top_focused - || self.is_top_overlay_active + ) || is_visible { + let redraw_request = shell.redraw_request(); + self.top.as_widget_mut().update( - top_tree, - event.clone(), - top_layout, - cursor, - renderer, - clipboard, - shell, - viewport, + top_tree, event, top_layout, cursor, renderer, clipboard, + shell, viewport, ); + + // Ignore redraw requests of invisible content + if !is_visible { + Shell::replace_redraw_request(shell, redraw_request); + } }; if shell.is_event_captured() { @@ -899,7 +902,7 @@ where self.base.as_widget_mut().update( base_tree, - event.clone(), + event, base_layout, cursor, renderer, @@ -1136,10 +1139,11 @@ where /// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; /// use iced::font; /// use iced::widget::{rich_text, span}; -/// use iced::{color, Font}; +/// use iced::{color, never, Font}; /// /// #[derive(Debug, Clone)] /// enum Message { +/// LinkClicked(&'static str), /// // ... /// } /// @@ -1149,13 +1153,14 @@ where /// span(" "), /// span("And I am bold!").font(Font { weight: font::Weight::Bold, ..Font::default() }), /// ]) +/// .on_link_click(never) /// .size(20) /// .into() /// } /// ``` -pub fn rich_text<'a, Link, Theme, Renderer>( +pub fn rich_text<'a, Link, Message, Theme, Renderer>( spans: impl AsRef<[text::Span<'a, Link, Renderer::Font>]> + 'a, -) -> text::Rich<'a, Link, Theme, Renderer> +) -> text::Rich<'a, Link, Message, Theme, Renderer> where Link: Clone + 'static, Theme: text::Catalog + 'a, @@ -1179,7 +1184,7 @@ where /// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; /// use iced::font; /// use iced::widget::{rich_text, span}; -/// use iced::{color, Font}; +/// use iced::{color, never, Font}; /// /// #[derive(Debug, Clone)] /// enum Message { @@ -1192,6 +1197,7 @@ where /// " ", /// span("And I am bold!").font(Font { weight: font::Weight::Bold, ..Font::default() }), /// ] +/// .on_link_click(never) /// .size(20) /// .into() /// } diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 20a7955f..811241a9 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -151,7 +151,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -167,7 +167,7 @@ where return; }; - match delta { + match *delta { mouse::ScrollDelta::Lines { y, .. } | mouse::ScrollDelta::Pixels { y, .. } => { let state = tree.state.downcast_mut::<State>(); @@ -215,6 +215,7 @@ where } } + shell.request_redraw(); shell.capture_event(); } Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { @@ -226,6 +227,8 @@ where state.cursor_grabbed_at = Some(cursor_position); state.starting_offset = state.current_offset; + + shell.request_redraw(); shell.capture_event(); } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { @@ -233,6 +236,7 @@ where if state.cursor_grabbed_at.is_some() { state.cursor_grabbed_at = None; + shell.request_redraw(); shell.capture_event(); } } @@ -256,7 +260,7 @@ where .max(0.0) .round(); - let delta = position - origin; + let delta = *position - origin; let x = if bounds.width < scaled_size.width { (state.starting_offset.x - delta.x) @@ -273,6 +277,7 @@ where }; state.current_offset = Vector::new(x, y); + shell.request_redraw(); shell.capture_event(); } } diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index ab0b0bde..313b728a 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -300,7 +300,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -315,13 +315,7 @@ where .zip(layout.children()) { child.as_widget_mut().update( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, + state, event, layout, cursor, renderer, clipboard, shell, viewport, ); } diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index c6710e30..6df026de 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -198,7 +198,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -386,7 +386,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index 15b8b62e..c215de7a 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -6,7 +6,6 @@ use crate::core::overlay; use crate::core::renderer; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; -use crate::core::window; use crate::core::{ self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, Widget, @@ -267,7 +266,10 @@ where state: tree::State::new(S::default()), children: vec![Tree::empty()], }))); + *self.tree.borrow_mut() = state.clone(); + self.diff_self(); + tree::State::new(state) } @@ -314,7 +316,7 @@ where fn update( &mut self, tree: &mut Tree, - event: core::Event, + event: &core::Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -344,17 +346,8 @@ where } local_shell.revalidate_layout(|| shell.invalidate_layout()); - - if let Some(redraw_request) = local_shell.redraw_request() { - match redraw_request { - window::RedrawRequest::NextFrame => { - shell.request_redraw(); - } - window::RedrawRequest::At(at) => { - shell.request_redraw_at(at); - } - } - } + shell.request_redraw_at(local_shell.redraw_request()); + shell.request_input_method(local_shell.input_method()); if !local_messages.is_empty() { let mut heads = self.state.take().unwrap().into_heads(); @@ -603,7 +596,7 @@ where fn update( &mut self, - event: core::Event, + event: &core::Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -629,17 +622,8 @@ where } local_shell.revalidate_layout(|| shell.invalidate_layout()); - - if let Some(redraw_request) = local_shell.redraw_request() { - match redraw_request { - window::RedrawRequest::NextFrame => { - shell.request_redraw(); - } - window::RedrawRequest::At(at) => { - shell.request_redraw_at(at); - } - } - } + shell.request_redraw_at(local_shell.redraw_request()); + shell.request_input_method(local_shell.input_method()); if !local_messages.is_empty() { let mut inner = diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index 8129336e..e7c937af 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -188,7 +188,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -416,7 +416,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/lib.rs b/widget/src/lib.rs index b8cfa98f..31dcc205 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -12,7 +12,6 @@ mod action; mod column; mod mouse_area; mod pin; -mod row; mod space; mod stack; mod themer; @@ -28,6 +27,7 @@ pub mod pick_list; pub mod pop; pub mod progress_bar; pub mod radio; +pub mod row; pub mod rule; pub mod scrollable; pub mod slider; diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index 858ee281..b69c663e 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -29,13 +29,9 @@ //! } //! //! fn view(&self) -> Element<'_, Message> { -//! markdown::view( -//! &self.markdown, -//! markdown::Settings::default(), -//! markdown::Style::from_palette(Theme::TokyoNightStorm.palette()), -//! ) -//! .map(Message::LinkClicked) -//! .into() +//! markdown::view(&self.markdown, Theme::TokyoNight) +//! .map(Message::LinkClicked) +//! .into() //! } //! //! fn update(state: &mut State, message: Message) { @@ -59,6 +55,7 @@ use crate::{column, container, rich_text, row, scrollable, span, text}; use std::borrow::BorrowMut; use std::cell::{Cell, RefCell}; use std::collections::{HashMap, HashSet}; +use std::mem; use std::ops::Range; use std::rc::Rc; use std::sync::Arc; @@ -144,6 +141,7 @@ impl Content { let mut state = State { leftover: String::new(), references: self.state.references.clone(), + images: HashSet::new(), highlighter: None, }; @@ -153,6 +151,7 @@ impl Content { self.items[*index] = item; } + self.state.images.extend(state.images.drain()); drop(state); } @@ -167,6 +166,11 @@ impl Content { pub fn items(&self) -> &[Item] { &self.items } + + /// Returns the URLs of the Markdown images present in the [`Content`]. + pub fn images(&self) -> &HashSet<Url> { + &self.state.images + } } /// A Markdown item. @@ -179,7 +183,14 @@ pub enum Item { /// A code block. /// /// You can enable the `highlighter` feature for syntax highlighting. - CodeBlock(Vec<Text>), + CodeBlock { + /// The language of the code block, if any. + language: Option<String>, + /// The raw code of the code block. + code: String, + /// The styled lines of text in the code block. + lines: Vec<Text>, + }, /// A list. List { /// The first number of the list, if it is ordered. @@ -187,6 +198,15 @@ pub enum Item { /// The items of the list. items: Vec<Vec<Item>>, }, + /// An image. + Image { + /// The destination URL of the image. + url: Url, + /// The title of the image. + title: String, + /// The alternative text of the image. + alt: Text, + }, } /// A bunch of parsed Markdown text. @@ -319,13 +339,9 @@ impl Span { /// } /// /// fn view(&self) -> Element<'_, Message> { -/// markdown::view( -/// &self.markdown, -/// markdown::Settings::default(), -/// markdown::Style::from_palette(Theme::TokyoNightStorm.palette()), -/// ) -/// .map(Message::LinkClicked) -/// .into() +/// markdown::view(&self.markdown, Theme::TokyoNight) +/// .map(Message::LinkClicked) +/// .into() /// } /// /// fn update(state: &mut State, message: Message) { @@ -346,6 +362,7 @@ pub fn parse(markdown: &str) -> impl Iterator<Item = Item> + '_ { struct State { leftover: String, references: HashMap<String, String>, + images: HashSet<Url>, #[cfg(feature = "highlighter")] highlighter: Option<Highlighter>, } @@ -367,7 +384,7 @@ impl Highlighter { parser: iced_highlighter::Stream::new( &iced_highlighter::Settings { theme: iced_highlighter::Theme::Base16Ocean, - token: language.to_string(), + token: language.to_owned(), }, ), language: language.to_owned(), @@ -436,6 +453,10 @@ fn parse_with<'a>( mut state: impl BorrowMut<State> + 'a, markdown: &'a str, ) -> impl Iterator<Item = (Item, &'a str, HashSet<String>)> + 'a { + enum Scope { + List(List), + } + struct List { start: Option<u64>, items: Vec<Vec<Item>>, @@ -444,14 +465,17 @@ fn parse_with<'a>( let broken_links = Rc::new(RefCell::new(HashSet::new())); let mut spans = Vec::new(); - let mut code = Vec::new(); + let mut code = String::new(); + let mut code_language = None; + let mut code_lines = Vec::new(); let mut strong = false; let mut emphasis = false; let mut strikethrough = false; let mut metadata = false; let mut table = false; let mut link = None; - let mut lists = Vec::new(); + let mut image = None; + let mut stack = Vec::new(); #[cfg(feature = "highlighter")] let mut highlighter = None; @@ -476,7 +500,7 @@ fn parse_with<'a>( )) } else { let _ = RefCell::borrow_mut(&broken_links) - .insert(broken_link.reference.to_string()); + .insert(broken_link.reference.into_string()); None } @@ -492,10 +516,18 @@ fn parse_with<'a>( } let produce = move |state: &mut State, - lists: &mut Vec<List>, + stack: &mut Vec<Scope>, item, source: Range<usize>| { - if lists.is_empty() { + if let Some(scope) = stack.last_mut() { + match scope { + Scope::List(list) => { + list.items.last_mut().expect("item context").push(item); + } + } + + None + } else { state.leftover = markdown[source.start..].to_owned(); Some(( @@ -503,16 +535,6 @@ fn parse_with<'a>( &markdown[source.start..source.end], broken_links.take(), )) - } else { - lists - .last_mut() - .expect("list context") - .items - .last_mut() - .expect("item context") - .push(item); - - None } }; @@ -549,35 +571,42 @@ fn parse_with<'a>( None } + pulldown_cmark::Tag::Image { + dest_url, title, .. + } if !metadata && !table => { + image = Url::parse(&dest_url) + .ok() + .map(|url| (url, title.into_string())); + None + } pulldown_cmark::Tag::List(first_item) if !metadata && !table => { let prev = if spans.is_empty() { None } else { produce( state.borrow_mut(), - &mut lists, + &mut stack, Item::Paragraph(Text::new(spans.drain(..).collect())), source, ) }; - lists.push(List { + stack.push(Scope::List(List { start: first_item, items: Vec::new(), - }); + })); prev } pulldown_cmark::Tag::Item => { - lists - .last_mut() - .expect("list context") - .items - .push(Vec::new()); + if let Some(Scope::List(list)) = stack.last_mut() { + list.items.push(Vec::new()); + } + None } pulldown_cmark::Tag::CodeBlock( - pulldown_cmark::CodeBlockKind::Fenced(_language), + pulldown_cmark::CodeBlockKind::Fenced(language), ) if !metadata && !table => { #[cfg(feature = "highlighter")] { @@ -587,9 +616,9 @@ fn parse_with<'a>( .highlighter .take() .filter(|highlighter| { - highlighter.language == _language.as_ref() + highlighter.language == language.as_ref() }) - .unwrap_or_else(|| Highlighter::new(&_language)); + .unwrap_or_else(|| Highlighter::new(&language)); highlighter.prepare(); @@ -597,12 +626,15 @@ fn parse_with<'a>( }); } + code_language = + (!language.is_empty()).then(|| language.into_string()); + let prev = if spans.is_empty() { None } else { produce( state.borrow_mut(), - &mut lists, + &mut stack, Item::Paragraph(Text::new(spans.drain(..).collect())), source, ) @@ -624,7 +656,7 @@ fn parse_with<'a>( pulldown_cmark::TagEnd::Heading(level) if !metadata && !table => { produce( state.borrow_mut(), - &mut lists, + &mut stack, Item::Heading(level, Text::new(spans.drain(..).collect())), source, ) @@ -646,12 +678,16 @@ fn parse_with<'a>( None } pulldown_cmark::TagEnd::Paragraph if !metadata && !table => { - produce( - state.borrow_mut(), - &mut lists, - Item::Paragraph(Text::new(spans.drain(..).collect())), - source, - ) + if spans.is_empty() { + None + } else { + produce( + state.borrow_mut(), + &mut stack, + Item::Paragraph(Text::new(spans.drain(..).collect())), + source, + ) + } } pulldown_cmark::TagEnd::Item if !metadata && !table => { if spans.is_empty() { @@ -659,18 +695,20 @@ fn parse_with<'a>( } else { produce( state.borrow_mut(), - &mut lists, + &mut stack, Item::Paragraph(Text::new(spans.drain(..).collect())), source, ) } } pulldown_cmark::TagEnd::List(_) if !metadata && !table => { - let list = lists.pop().expect("list context"); + let scope = stack.pop()?; + + let Scope::List(list) = scope; produce( state.borrow_mut(), - &mut lists, + &mut stack, Item::List { start: list.start, items: list.items, @@ -678,6 +716,20 @@ fn parse_with<'a>( source, ) } + pulldown_cmark::TagEnd::Image if !metadata && !table => { + let (url, title) = image.take()?; + let alt = Text::new(spans.drain(..).collect()); + + let state = state.borrow_mut(); + let _ = state.images.insert(url.clone()); + + produce( + state, + &mut stack, + Item::Image { url, title, alt }, + source, + ) + } pulldown_cmark::TagEnd::CodeBlock if !metadata && !table => { #[cfg(feature = "highlighter")] { @@ -686,8 +738,12 @@ fn parse_with<'a>( produce( state.borrow_mut(), - &mut lists, - Item::CodeBlock(code.drain(..).collect()), + &mut stack, + Item::CodeBlock { + language: code_language.take(), + code: mem::take(&mut code), + lines: code_lines.drain(..).collect(), + }, source, ) } @@ -704,8 +760,10 @@ fn parse_with<'a>( pulldown_cmark::Event::Text(text) if !metadata && !table => { #[cfg(feature = "highlighter")] if let Some(highlighter) = &mut highlighter { + code.push_str(&text); + for line in text.lines() { - code.push(Text::new( + code_lines.push(Text::new( highlighter.highlight_line(line).to_vec(), )); } @@ -786,15 +844,25 @@ pub struct Settings { pub code_size: Pixels, /// The spacing to be used between elements. pub spacing: Pixels, + /// The styling of the Markdown. + pub style: Style, } impl Settings { + /// Creates new [`Settings`] with default text size and the given [`Style`]. + pub fn with_style(style: impl Into<Style>) -> Self { + Self::with_text_size(16, style) + } + /// Creates new [`Settings`] with the given base text size in [`Pixels`]. /// /// Heading levels will be adjusted automatically. Specifically, /// the first level will be twice the base size, and then every level /// after that will be 25% smaller. - pub fn with_text_size(text_size: impl Into<Pixels>) -> Self { + pub fn with_text_size( + text_size: impl Into<Pixels>, + style: impl Into<Style>, + ) -> Self { let text_size = text_size.into(); Self { @@ -807,13 +875,20 @@ impl Settings { h6_size: text_size, code_size: text_size * 0.75, spacing: text_size * 0.875, + style: style.into(), } } } -impl Default for Settings { - fn default() -> Self { - Self::with_text_size(16) +impl From<&Theme> for Settings { + fn from(theme: &Theme) -> Self { + Self::with_style(Style::from(theme)) + } +} + +impl From<Theme> for Settings { + fn from(theme: Theme) -> Self { + Self::with_style(Style::from(theme)) } } @@ -845,6 +920,24 @@ impl Style { } } +impl From<theme::Palette> for Style { + fn from(palette: theme::Palette) -> Self { + Self::from_palette(palette) + } +} + +impl From<&Theme> for Style { + fn from(theme: &Theme) -> Self { + Self::from_palette(theme.palette()) + } +} + +impl From<Theme> for Style { + fn from(theme: Theme) -> Self { + Self::from_palette(theme.palette()) + } +} + /// Display a bunch of Markdown items. /// /// You can obtain the items with [`parse`]. @@ -873,13 +966,9 @@ impl Style { /// } /// /// fn view(&self) -> Element<'_, Message> { -/// markdown::view( -/// &self.markdown, -/// markdown::Settings::default(), -/// markdown::Style::from_palette(Theme::TokyoNightStorm.palette()), -/// ) -/// .map(Message::LinkClicked) -/// .into() +/// markdown::view(&self.markdown, Theme::TokyoNight) +/// .map(Message::LinkClicked) +/// .into() /// } /// /// fn update(state: &mut State, message: Message) { @@ -891,109 +980,345 @@ impl Style { /// } /// } /// ``` -pub fn view<'a, 'b, Theme, Renderer>( - items: impl IntoIterator<Item = &'b Item>, - settings: Settings, - style: Style, +pub fn view<'a, Theme, Renderer>( + items: impl IntoIterator<Item = &'a Item>, + settings: impl Into<Settings>, ) -> Element<'a, Url, Theme, Renderer> where Theme: Catalog + 'a, Renderer: core::text::Renderer<Font = Font> + 'a, { + view_with(items, settings, &DefaultViewer) +} + +/// Runs [`view`] but with a custom [`Viewer`] to turn an [`Item`] into +/// an [`Element`]. +/// +/// This is useful if you want to customize the look of certain Markdown +/// elements. +pub fn view_with<'a, Message, Theme, Renderer>( + items: impl IntoIterator<Item = &'a Item>, + settings: impl Into<Settings>, + viewer: &impl Viewer<'a, Message, Theme, Renderer>, +) -> Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: Catalog + 'a, + Renderer: core::text::Renderer<Font = Font> + 'a, +{ + let settings = settings.into(); + + let blocks = items + .into_iter() + .enumerate() + .map(|(i, item_)| item(viewer, settings, item_, i)); + + Element::new(column(blocks).spacing(settings.spacing)) +} + +/// Displays an [`Item`] using the given [`Viewer`]. +pub fn item<'a, Message, Theme, Renderer>( + viewer: &impl Viewer<'a, Message, Theme, Renderer>, + settings: Settings, + item: &'a Item, + index: usize, +) -> Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: Catalog + 'a, + Renderer: core::text::Renderer<Font = Font> + 'a, +{ + match item { + Item::Image { url, title, alt } => { + viewer.image(settings, url, title, alt) + } + Item::Heading(level, text) => { + viewer.heading(settings, level, text, index) + } + Item::Paragraph(text) => viewer.paragraph(settings, text), + Item::CodeBlock { + language, + code, + lines, + } => viewer.code_block(settings, language.as_deref(), code, lines), + Item::List { start: None, items } => { + viewer.unordered_list(settings, items) + } + Item::List { + start: Some(start), + items, + } => viewer.ordered_list(settings, *start, items), + } +} + +/// Displays a heading using the default look. +pub fn heading<'a, Message, Theme, Renderer>( + settings: Settings, + level: &'a HeadingLevel, + text: &'a Text, + index: usize, + on_link_click: impl Fn(Url) -> Message + 'a, +) -> Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: Catalog + 'a, + Renderer: core::text::Renderer<Font = Font> + 'a, +{ let Settings { - text_size, h1_size, h2_size, h3_size, h4_size, h5_size, h6_size, - code_size, - spacing, + text_size, + .. } = settings; - let blocks = items.into_iter().enumerate().map(|(i, item)| match item { - Item::Heading(level, heading) => { - container(rich_text(heading.spans(style)).size(match level { + container( + rich_text(text.spans(settings.style)) + .on_link_click(on_link_click) + .size(match level { pulldown_cmark::HeadingLevel::H1 => h1_size, pulldown_cmark::HeadingLevel::H2 => h2_size, pulldown_cmark::HeadingLevel::H3 => h3_size, pulldown_cmark::HeadingLevel::H4 => h4_size, pulldown_cmark::HeadingLevel::H5 => h5_size, pulldown_cmark::HeadingLevel::H6 => h6_size, - })) - .padding(padding::top(if i > 0 { - text_size / 2.0 - } else { - Pixels::ZERO - })) - .into() - } - Item::Paragraph(paragraph) => { - rich_text(paragraph.spans(style)).size(text_size).into() - } - Item::List { start: None, items } => { - column(items.iter().map(|items| { - row![ - text("•").size(text_size), - view( - items, - Settings { - spacing: settings.spacing * 0.6, - ..settings - }, - style - ) - ] - .spacing(spacing) - .into() - })) - .spacing(spacing * 0.75) - .into() - } - Item::List { - start: Some(start), - items, - } => column(items.iter().enumerate().map(|(i, items)| { - row![ - text!("{}.", i as u64 + *start).size(text_size), - view( - items, - Settings { - spacing: settings.spacing * 0.6, - ..settings - }, - style - ) - ] - .spacing(spacing) - .into() - })) - .spacing(spacing * 0.75) - .into(), - Item::CodeBlock(lines) => container( - scrollable( - container(column(lines.iter().map(|line| { - rich_text(line.spans(style)) - .font(Font::MONOSPACE) - .size(code_size) - .into() - }))) - .padding(spacing.0 / 2.0), + }), + ) + .padding(padding::top(if index > 0 { + text_size / 2.0 + } else { + Pixels::ZERO + })) + .into() +} + +/// Displays a paragraph using the default look. +pub fn paragraph<'a, Message, Theme, Renderer>( + settings: Settings, + text: &'a Text, + on_link_click: impl Fn(Url) -> Message + 'a, +) -> Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: Catalog + 'a, + Renderer: core::text::Renderer<Font = Font> + 'a, +{ + rich_text(text.spans(settings.style)) + .size(settings.text_size) + .on_link_click(on_link_click) + .into() +} + +/// Displays an unordered list using the default look and +/// calling the [`Viewer`] for each bullet point item. +pub fn unordered_list<'a, Message, Theme, Renderer>( + viewer: &impl Viewer<'a, Message, Theme, Renderer>, + settings: Settings, + items: &'a [Vec<Item>], +) -> Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: Catalog + 'a, + Renderer: core::text::Renderer<Font = Font> + 'a, +{ + column(items.iter().map(|items| { + row![ + text("•").size(settings.text_size), + view_with( + items, + Settings { + spacing: settings.spacing * 0.6, + ..settings + }, + viewer, + ) + ] + .spacing(settings.spacing) + .into() + })) + .spacing(settings.spacing * 0.75) + .padding([0.0, settings.spacing.0]) + .into() +} + +/// Displays an ordered list using the default look and +/// calling the [`Viewer`] for each numbered item. +pub fn ordered_list<'a, Message, Theme, Renderer>( + viewer: &impl Viewer<'a, Message, Theme, Renderer>, + settings: Settings, + start: u64, + items: &'a [Vec<Item>], +) -> Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: Catalog + 'a, + Renderer: core::text::Renderer<Font = Font> + 'a, +{ + column(items.iter().enumerate().map(|(i, items)| { + row![ + text!("{}.", i as u64 + start).size(settings.text_size), + view_with( + items, + Settings { + spacing: settings.spacing * 0.6, + ..settings + }, + viewer, ) - .direction(scrollable::Direction::Horizontal( - scrollable::Scrollbar::default() - .width(spacing.0 / 2.0) - .scroller_width(spacing.0 / 2.0), - )), + ] + .spacing(settings.spacing) + .into() + })) + .spacing(settings.spacing * 0.75) + .padding([0.0, settings.spacing.0]) + .into() +} + +/// Displays a code block using the default look. +pub fn code_block<'a, Message, Theme, Renderer>( + settings: Settings, + lines: &'a [Text], + on_link_click: impl Fn(Url) -> Message + Clone + 'a, +) -> Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: Catalog + 'a, + Renderer: core::text::Renderer<Font = Font> + 'a, +{ + container( + scrollable( + container(column(lines.iter().map(|line| { + rich_text(line.spans(settings.style)) + .on_link_click(on_link_click.clone()) + .font(Font::MONOSPACE) + .size(settings.code_size) + .into() + }))) + .padding(settings.code_size), ) - .width(Length::Fill) - .padding(spacing.0 / 2.0) + .direction(scrollable::Direction::Horizontal( + scrollable::Scrollbar::default() + .width(settings.code_size / 2) + .scroller_width(settings.code_size / 2), + )), + ) + .width(Length::Fill) + .padding(settings.code_size / 4) + .class(Theme::code_block()) + .into() +} + +/// A view strategy to display a Markdown [`Item`].j +pub trait Viewer<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> +where + Self: Sized + 'a, + Message: 'a, + Theme: Catalog + 'a, + Renderer: core::text::Renderer<Font = Font> + 'a, +{ + /// Produces a message when a link is clicked with the given [`Url`]. + fn on_link_click(url: Url) -> Message; + + /// Displays an image. + /// + /// By default, it will show a container with the image title. + fn image( + &self, + settings: Settings, + url: &'a Url, + title: &'a str, + alt: &Text, + ) -> Element<'a, Message, Theme, Renderer> { + let _url = url; + let _title = title; + + container( + rich_text(alt.spans(settings.style)) + .on_link_click(Self::on_link_click), + ) + .padding(settings.spacing.0) .class(Theme::code_block()) - .into(), - }); + .into() + } + + /// Displays a heading. + /// + /// By default, it calls [`heading`]. + fn heading( + &self, + settings: Settings, + level: &'a HeadingLevel, + text: &'a Text, + index: usize, + ) -> Element<'a, Message, Theme, Renderer> { + heading(settings, level, text, index, Self::on_link_click) + } - Element::new(column(blocks).spacing(spacing)) + /// Displays a paragraph. + /// + /// By default, it calls [`paragraph`]. + fn paragraph( + &self, + settings: Settings, + text: &'a Text, + ) -> Element<'a, Message, Theme, Renderer> { + paragraph(settings, text, Self::on_link_click) + } + + /// Displays a code block. + /// + /// By default, it calls [`code_block`]. + fn code_block( + &self, + settings: Settings, + language: Option<&'a str>, + code: &'a str, + lines: &'a [Text], + ) -> Element<'a, Message, Theme, Renderer> { + let _language = language; + let _code = code; + + code_block(settings, lines, Self::on_link_click) + } + + /// Displays an unordered list. + /// + /// By default, it calls [`unordered_list`]. + fn unordered_list( + &self, + settings: Settings, + items: &'a [Vec<Item>], + ) -> Element<'a, Message, Theme, Renderer> { + unordered_list(self, settings, items) + } + + /// Displays an ordered list. + /// + /// By default, it calls [`ordered_list`]. + fn ordered_list( + &self, + settings: Settings, + start: u64, + items: &'a [Vec<Item>], + ) -> Element<'a, Message, Theme, Renderer> { + ordered_list(self, settings, start, items) + } +} + +#[derive(Debug, Clone, Copy)] +struct DefaultViewer; + +impl<'a, Theme, Renderer> Viewer<'a, Url, Theme, Renderer> for DefaultViewer +where + Theme: Catalog + 'a, + Renderer: core::text::Renderer<Font = Font> + 'a, +{ + fn on_link_click(url: Url) -> Url { + url + } } /// The theme catalog of Markdown items. diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 9ba3cff5..c1c3ba0f 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -218,7 +218,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -228,7 +228,7 @@ where ) { self.content.as_widget_mut().update( &mut tree.children[0], - event.clone(), + event, layout, cursor, renderer, @@ -326,7 +326,7 @@ where fn update<Message: Clone, Theme, Renderer>( widget: &mut MouseArea<'_, Message, Theme, Renderer>, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, @@ -425,7 +425,7 @@ fn update<Message: Clone, Theme, Renderer>( } Event::Mouse(mouse::Event::WheelScrolled { delta }) => { if let Some(on_scroll) = widget.on_scroll.as_ref() { - shell.publish(on_scroll(delta)); + shell.publish(on_scroll(*delta)); shell.capture_event(); } } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 611476ce..9d0539ff 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -263,7 +263,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -388,7 +388,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 5c3b343c..3ae1dfc7 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -474,7 +474,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -509,15 +509,8 @@ where let is_picked = picked_pane == Some(pane); content.update( - tree, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, - viewport, - is_picked, + tree, event, layout, cursor, renderer, clipboard, shell, + viewport, is_picked, ); } @@ -687,7 +680,7 @@ where _ => {} } - if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) { + if shell.redraw_request() != window::RedrawRequest::NextFrame { let interaction = self .grid_interaction(action, layout, cursor) .or_else(|| { diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index be5e5066..4d63dd18 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -242,7 +242,7 @@ where pub(crate) fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -256,7 +256,7 @@ where title_bar.update( &mut tree.children[1], - event.clone(), + event, children.next().unwrap(), cursor, renderer, diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 4bd2c2f6..611c3d67 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -430,7 +430,7 @@ where pub(crate) fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -456,7 +456,7 @@ where compact.as_widget_mut().update( &mut tree.children[2], - event.clone(), + event, compact_layout, cursor, renderer, @@ -469,7 +469,7 @@ where controls.full.as_widget_mut().update( &mut tree.children[1], - event.clone(), + event, controls_layout, cursor, renderer, @@ -481,7 +481,7 @@ where } else { controls.full.as_widget_mut().update( &mut tree.children[1], - event.clone(), + event, controls_layout, cursor, renderer, diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 6708e7cd..b751fcc3 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -430,7 +430,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, @@ -489,13 +489,13 @@ where let options = self.options.borrow(); let selected = self.selected.as_ref().map(Borrow::borrow); - let next_option = if y < 0.0 { + let next_option = if *y < 0.0 { if let Some(selected) = selected { find_next(selected, options.iter()) } else { options.first() } - } else if y > 0.0 { + } else if *y > 0.0 { if let Some(selected) = selected { find_next(selected, options.iter().rev()) } else { @@ -513,7 +513,7 @@ where } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - state.keyboard_modifiers = modifiers; + state.keyboard_modifiers = *modifiers; } _ => {} }; diff --git a/widget/src/pin.rs b/widget/src/pin.rs index 7c1aca61..afa29398 100644 --- a/widget/src/pin.rs +++ b/widget/src/pin.rs @@ -177,7 +177,7 @@ where fn update( &mut self, tree: &mut widget::Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/pop.rs b/widget/src/pop.rs index 146cfb2b..950371ea 100644 --- a/widget/src/pop.rs +++ b/widget/src/pop.rs @@ -3,6 +3,7 @@ use crate::core::layout; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; +use crate::core::text; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::window; @@ -17,6 +18,7 @@ use crate::core::{ #[allow(missing_debug_implementations)] pub struct Pop<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> { content: Element<'a, Message, Theme, Renderer>, + key: Option<text::Fragment<'a>>, on_show: Option<Box<dyn Fn(Size) -> Message + 'a>>, on_resize: Option<Box<dyn Fn(Size) -> Message + 'a>>, on_hide: Option<Message>, @@ -34,6 +36,7 @@ where ) -> Self { Self { content: content.into(), + key: None, on_show: None, on_resize: None, on_hide: None, @@ -66,6 +69,14 @@ where self } + /// Sets the key of the [`Pop`] widget, for continuity. + /// + /// If the key changes, the [`Pop`] widget will trigger again. + pub fn key(mut self, key: impl text::IntoFragment<'a>) -> Self { + self.key = Some(key.into_fragment()); + self + } + /// Sets the distance in [`Pixels`] to use in anticipation of the /// content popping into view. /// @@ -77,10 +88,11 @@ where } } -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Default)] struct State { has_popped_in: bool, last_size: Option<Size>, + last_key: Option<String>, } impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> @@ -108,7 +120,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -118,8 +130,16 @@ where ) { if let Event::Window(window::Event::RedrawRequested(_)) = &event { let state = tree.state.downcast_mut::<State>(); - let bounds = layout.bounds(); + if state.has_popped_in + && state.last_key.as_deref() != self.key.as_deref() + { + state.has_popped_in = false; + state.last_key = + self.key.as_ref().cloned().map(text::Fragment::into_owned); + } + + let bounds = layout.bounds(); let top_left_distance = viewport.distance(bounds.position()); let bottom_right_distance = viewport diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 15c983df..0df4d715 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -326,7 +326,7 @@ where fn update( &mut self, _state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/widget/src/row.rs b/widget/src/row.rs index 3b605f07..5ffeab49 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -256,7 +256,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -271,13 +271,7 @@ where .zip(layout.children()) { child.as_widget_mut().update( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, + state, event, layout, cursor, renderer, clipboard, shell, viewport, ); } @@ -495,7 +489,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 312aee29..0cf75c04 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -33,8 +33,9 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ - self, Background, Clipboard, Color, Element, Event, Layout, Length, - Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, + self, Background, Clipboard, Color, Element, Event, InputMethod, Layout, + Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, + Widget, }; use crate::runtime::task::{self, Task}; use crate::runtime::Action; @@ -516,7 +517,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -563,7 +564,8 @@ where Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { if let Some(scrollbar) = scrollbars.y { - let Some(cursor_position) = cursor.position() + let Some(cursor_position) = + cursor.land().position() else { return; }; @@ -635,7 +637,8 @@ where match event { Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { - let Some(cursor_position) = cursor.position() else { + let Some(cursor_position) = cursor.land().position() + else { return; }; @@ -726,12 +729,14 @@ where _ => mouse::Cursor::Unavailable, }; + let had_input_method = shell.input_method().is_enabled(); + let translation = state.translation(self.direction, bounds, content_bounds); self.content.as_widget_mut().update( &mut tree.children[0], - event.clone(), + event, content, cursor, renderer, @@ -743,6 +748,14 @@ where ..bounds }, ); + + if !had_input_method { + if let InputMethod::Enabled { position, .. } = + shell.input_method_mut() + { + *position = *position - translation; + } + } }; if matches!( @@ -768,7 +781,7 @@ where modifiers, )) = event { - state.keyboard_modifiers = modifiers; + state.keyboard_modifiers = *modifiers; return; } @@ -779,7 +792,7 @@ where return; } - let delta = match delta { + let delta = match *delta { mouse::ScrollDelta::Lines { x, y } => { let is_shift_pressed = state.keyboard_modifiers.shift(); diff --git a/widget/src/shader.rs b/widget/src/shader.rs index 8ec57482..06254a1c 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -9,7 +9,6 @@ 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, Event, Length, Rectangle, Shell, Size}; use crate::renderer::wgpu::primitive; @@ -89,7 +88,7 @@ where fn update( &mut self, tree: &mut Tree, - event: crate::core::Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, @@ -105,21 +104,12 @@ where { let (message, redraw_request, event_status) = action.into_inner(); + shell.request_redraw_at(redraw_request); + if let Some(message) = message { shell.publish(message); } - if let Some(redraw_request) = redraw_request { - match redraw_request { - window::RedrawRequest::NextFrame => { - shell.request_redraw(); - } - window::RedrawRequest::At(at) => { - shell.request_redraw_at(at); - } - } - } - if event_status == event::Status::Captured { shell.capture_event(); } @@ -184,7 +174,7 @@ where fn update( &self, state: &mut Self::State, - event: Event, + event: &Event, bounds: Rectangle, cursor: mouse::Cursor, ) -> Option<Action<Message>> { diff --git a/widget/src/shader/program.rs b/widget/src/shader/program.rs index 0fc110af..81ecc9b1 100644 --- a/widget/src/shader/program.rs +++ b/widget/src/shader/program.rs @@ -26,7 +26,7 @@ pub trait Program<Message> { fn update( &self, _state: &mut Self::State, - _event: shader::Event, + _event: &shader::Event, _bounds: Rectangle, _cursor: mouse::Cursor, ) -> Option<Action<Message>> { diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 52500854..1908abc9 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -245,7 +245,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/widget/src/stack.rs b/widget/src/stack.rs index 12ed941d..df9f6162 100644 --- a/widget/src/stack.rs +++ b/widget/src/stack.rs @@ -207,7 +207,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, mut cursor: mouse::Cursor, renderer: &Renderer, @@ -216,43 +216,34 @@ where viewport: &Rectangle, ) { let is_over = cursor.is_over(layout.bounds()); - let is_mouse_movement = - matches!(event, Event::Mouse(mouse::Event::CursorMoved { .. })); + let end = self.children.len() - 1; - for ((child, state), layout) in self + for (i, ((child, state), layout)) in self .children .iter_mut() .rev() .zip(tree.children.iter_mut().rev()) .zip(layout.children().rev()) + .enumerate() { child.as_widget_mut().update( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, + state, event, layout, cursor, renderer, clipboard, shell, viewport, ); - if is_over - && !is_mouse_movement - && cursor != mouse::Cursor::Unavailable - { + if shell.is_event_captured() { + return; + } + + if i < end && is_over && !cursor.is_levitating() { let interaction = child.as_widget().mouse_interaction( state, layout, cursor, viewport, renderer, ); if interaction != mouse::Interaction::None { - cursor = mouse::Cursor::Unavailable; + cursor = cursor.levitate(); } } - - if shell.is_event_captured() { - return; - } } } diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index 0b499ec6..4d4a2861 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -14,8 +14,13 @@ use crate::core::{ /// A bunch of [`Rich`] text. #[allow(missing_debug_implementations)] -pub struct Rich<'a, Link, Theme = crate::Theme, Renderer = crate::Renderer> -where +pub struct Rich< + 'a, + Link, + Message, + Theme = crate::Theme, + Renderer = crate::Renderer, +> where Link: Clone + 'static, Theme: Catalog, Renderer: core::text::Renderer, @@ -31,9 +36,11 @@ where wrapping: Wrapping, class: Theme::Class<'a>, hovered_link: Option<usize>, + on_link_click: Option<Box<dyn Fn(Link) -> Message + 'a>>, } -impl<'a, Link, Theme, Renderer> Rich<'a, Link, Theme, Renderer> +impl<'a, Link, Message, Theme, Renderer> + Rich<'a, Link, Message, Theme, Renderer> where Link: Clone + 'static, Theme: Catalog, @@ -54,6 +61,7 @@ where wrapping: Wrapping::default(), class: Theme::default(), hovered_link: None, + on_link_click: None, } } @@ -127,6 +135,16 @@ where self } + /// Sets the message that will be produced when a link of the [`Rich`] text + /// is clicked. + pub fn on_link_click( + mut self, + on_link_clicked: impl Fn(Link) -> Message + 'a, + ) -> Self { + self.on_link_click = Some(Box::new(on_link_clicked)); + self + } + /// Sets the default style of the [`Rich`] text. #[must_use] pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self @@ -164,7 +182,8 @@ where } } -impl<'a, Link, Theme, Renderer> Default for Rich<'a, Link, Theme, Renderer> +impl<'a, Link, Message, Theme, Renderer> Default + for Rich<'a, Link, Message, Theme, Renderer> where Link: Clone + 'a, Theme: Catalog, @@ -182,8 +201,8 @@ struct State<Link, P: Paragraph> { paragraph: P, } -impl<Link, Theme, Renderer> Widget<Link, Theme, Renderer> - for Rich<'_, Link, Theme, Renderer> +impl<Link, Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Rich<'_, Link, Message, Theme, Renderer> where Link: Clone + 'static, Theme: Catalog, @@ -252,7 +271,8 @@ where let style = theme.style(&self.class); for (index, span) in self.spans.as_ref().as_ref().iter().enumerate() { - let is_hovered_link = Some(index) == self.hovered_link; + let is_hovered_link = self.on_link_click.is_some() + && Some(index) == self.hovered_link; if span.highlight.is_some() || span.underline @@ -358,14 +378,18 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Link>, + shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) { + let Some(on_link_clicked) = &self.on_link_click else { + return; + }; + let was_hovered = self.hovered_link.is_some(); if let Some(position) = cursor.position_in(layout.bounds()) { @@ -414,7 +438,7 @@ where .get(span) .and_then(|span| span.link.clone()) { - shell.publish(link); + shell.publish(on_link_clicked(link)); } } _ => {} @@ -509,8 +533,9 @@ where }) } -impl<'a, Link, Theme, Renderer> FromIterator<Span<'a, Link, Renderer::Font>> - for Rich<'a, Link, Theme, Renderer> +impl<'a, Link, Message, Theme, Renderer> + FromIterator<Span<'a, Link, Renderer::Font>> + for Rich<'a, Link, Message, Theme, Renderer> where Link: Clone + 'a, Theme: Catalog, @@ -524,16 +549,18 @@ where } } -impl<'a, Link, Theme, Renderer> From<Rich<'a, Link, Theme, Renderer>> - for Element<'a, Link, Theme, Renderer> +impl<'a, Link, Message, Theme, Renderer> + From<Rich<'a, Link, Message, Theme, Renderer>> + for Element<'a, Message, Theme, Renderer> where + Message: 'a, Link: Clone + 'a, Theme: Catalog + 'a, Renderer: core::text::Renderer + 'a, { fn from( - text: Rich<'a, Link, Theme, Renderer>, - ) -> Element<'a, Link, Theme, Renderer> { + text: Rich<'a, Link, Message, Theme, Renderer>, + ) -> Element<'a, Message, Theme, Renderer> { Element::new(text) } } diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index f1ec589b..7e40a56a 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -33,6 +33,7 @@ //! ``` use crate::core::alignment; use crate::core::clipboard::{self, Clipboard}; +use crate::core::input_method; use crate::core::keyboard; use crate::core::keyboard::key; use crate::core::layout::{self, Layout}; @@ -46,14 +47,15 @@ use crate::core::widget::operation; use crate::core::widget::{self, Widget}; use crate::core::window; use crate::core::{ - Background, Border, Color, Element, Event, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, SmolStr, Theme, Vector, + Background, Border, Color, Element, Event, InputMethod, Length, Padding, + Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector, }; use std::borrow::Cow; use std::cell::RefCell; use std::fmt; use std::ops::DerefMut; +use std::ops::Range; use std::sync::Arc; pub use text::editor::{Action, Edit, Line, LineEnding, Motion}; @@ -322,6 +324,47 @@ where self.class = class.into(); self } + + fn input_method<'b>( + &self, + state: &'b State<Highlighter>, + renderer: &Renderer, + layout: Layout<'_>, + ) -> InputMethod<&'b str> { + let Some(Focus { + is_window_focused: true, + .. + }) = &state.focus + else { + return InputMethod::Disabled; + }; + + let bounds = layout.bounds(); + let internal = self.content.0.borrow_mut(); + + let text_bounds = bounds.shrink(self.padding); + let translation = text_bounds.position() - Point::ORIGIN; + + let cursor = match internal.editor.cursor() { + Cursor::Caret(position) => position, + Cursor::Selection(ranges) => { + ranges.first().cloned().unwrap_or_default().position() + } + }; + + let line_height = self.line_height.to_absolute( + self.text_size.unwrap_or_else(|| renderer.default_size()), + ); + + let position = + cursor + translation + Vector::new(0.0, f32::from(line_height)); + + InputMethod::Enabled { + position, + purpose: input_method::Purpose::Normal, + preedit: state.preedit.as_ref().map(input_method::Preedit::as_ref), + } + } } /// The content of a [`TextEditor`]. @@ -450,6 +493,7 @@ where #[derive(Debug)] pub struct State<Highlighter: text::Highlighter> { focus: Option<Focus>, + preedit: Option<input_method::Preedit>, last_click: Option<mouse::Click>, drag_click: Option<mouse::click::Kind>, partial_scroll: f32, @@ -458,7 +502,7 @@ pub struct State<Highlighter: text::Highlighter> { highlighter_format_address: usize, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] struct Focus { updated_at: Instant, now: Instant, @@ -524,6 +568,7 @@ where fn state(&self) -> widget::tree::State { widget::tree::State::new(State { focus: None, + preedit: None, last_click: None, drag_click: None, partial_scroll: 0.0, @@ -602,10 +647,10 @@ where fn update( &mut self, tree: &mut widget::Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, - _renderer: &Renderer, + renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, @@ -637,17 +682,18 @@ where Event::Window(window::Event::RedrawRequested(now)) => { if let Some(focus) = &mut state.focus { if focus.is_window_focused { - focus.now = now; + focus.now = *now; let millis_until_redraw = Focus::CURSOR_BLINK_INTERVAL_MILLIS - - (now - focus.updated_at).as_millis() + - (focus.now - focus.updated_at).as_millis() % Focus::CURSOR_BLINK_INTERVAL_MILLIS; shell.request_redraw_at( - now + Duration::from_millis( - millis_until_redraw as u64, - ), + focus.now + + Duration::from_millis( + millis_until_redraw as u64, + ), ); } } @@ -701,6 +747,28 @@ where })); shell.capture_event(); } + Update::InputMethod(update) => match update { + Ime::Toggle(is_open) => { + state.preedit = + is_open.then(input_method::Preedit::new); + + shell.request_redraw(); + } + Ime::Preedit { content, selection } => { + state.preedit = Some(input_method::Preedit { + content, + selection, + text_size: self.text_size, + }); + + shell.request_redraw(); + } + Ime::Commit(text) => { + shell.publish(on_edit(Action::Edit(Edit::Paste( + Arc::new(text), + )))); + } + }, Update::Binding(binding) => { fn apply_binding< H: text::Highlighter, @@ -827,6 +895,10 @@ where if is_redraw { self.last_status = Some(status); + + shell.request_input_method( + &self.input_method(state, renderer, layout), + ); } else if self .last_status .is_some_and(|last_status| status != last_status) @@ -1129,12 +1201,22 @@ enum Update<Message> { Drag(Point), Release, Scroll(f32), + InputMethod(Ime), Binding(Binding<Message>), } +enum Ime { + Toggle(bool), + Preedit { + content: String, + selection: Option<Range<usize>>, + }, + Commit(String), +} + impl<Message> Update<Message> { fn from_event<H: Highlighter>( - event: Event, + event: &Event, state: &State<H>, bounds: Rectangle, padding: Padding, @@ -1191,6 +1273,28 @@ impl<Message> Update<Message> { } _ => None, }, + Event::InputMethod(event) => match event { + input_method::Event::Opened | input_method::Event::Closed => { + Some(Update::InputMethod(Ime::Toggle(matches!( + event, + input_method::Event::Opened + )))) + } + input_method::Event::Preedit(content, selection) + if state.focus.is_some() => + { + Some(Update::InputMethod(Ime::Preedit { + content: content.clone(), + selection: selection.clone(), + })) + } + input_method::Event::Commit(content) + if state.focus.is_some() => + { + Some(Update::InputMethod(Ime::Commit(content.clone()))) + } + _ => None, + }, Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, @@ -1206,9 +1310,9 @@ impl<Message> Update<Message> { }; let key_press = KeyPress { - key, - modifiers, - text, + key: key.clone(), + modifiers: *modifiers, + text: text.clone(), status, }; diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 57ebe46a..ae3dfe4c 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -42,6 +42,7 @@ use editor::Editor; use crate::core::alignment; use crate::core::clipboard::{self, Clipboard}; +use crate::core::input_method; use crate::core::keyboard; use crate::core::keyboard::key; use crate::core::layout; @@ -56,8 +57,8 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ - Background, Border, Color, Element, Event, Layout, Length, Padding, Pixels, - Point, Rectangle, Shell, Size, Theme, Vector, Widget, + Background, Border, Color, Element, Event, InputMethod, Layout, Length, + Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; use crate::runtime::task::{self, Task}; use crate::runtime::Action; @@ -391,6 +392,54 @@ where } } + fn input_method<'b>( + &self, + state: &'b State<Renderer::Paragraph>, + layout: Layout<'_>, + value: &Value, + ) -> InputMethod<&'b str> { + let Some(Focus { + is_window_focused: true, + .. + }) = &state.is_focused + else { + return InputMethod::Disabled; + }; + + let secure_value = self.is_secure.then(|| value.secure()); + let value = secure_value.as_ref().unwrap_or(value); + + let text_bounds = layout.children().next().unwrap().bounds(); + + let caret_index = match state.cursor.state(value) { + cursor::State::Index(position) => position, + cursor::State::Selection { start, end } => start.min(end), + }; + + let text = state.value.raw(); + let (cursor_x, scroll_offset) = + measure_cursor_and_scroll_offset(text, text_bounds, caret_index); + + let alignment_offset = alignment_offset( + text_bounds.width, + text.min_width(), + self.alignment, + ); + + let x = (text_bounds.x + cursor_x).floor() - scroll_offset + + alignment_offset; + + InputMethod::Enabled { + position: Point::new(x, text_bounds.y + text_bounds.height), + purpose: if self.is_secure { + input_method::Purpose::Secure + } else { + input_method::Purpose::Normal + }, + preedit: state.preedit.as_ref().map(input_method::Preedit::as_ref), + } + } + /// Draws the [`TextInput`] with the given [`Renderer`], overriding its /// [`Value`] if provided. /// @@ -529,7 +578,13 @@ where }; let draw = |renderer: &mut Renderer, viewport| { - let paragraph = if text.is_empty() { + let paragraph = if text.is_empty() + && state + .preedit + .as_ref() + .map(|preedit| preedit.content.is_empty()) + .unwrap_or(true) + { state.placeholder.raw() } else { state.value.raw() @@ -639,7 +694,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -1197,6 +1252,52 @@ where state.keyboard_modifiers = *modifiers; } + Event::InputMethod(event) => match event { + input_method::Event::Opened | input_method::Event::Closed => { + let state = state::<Renderer>(tree); + + state.preedit = + matches!(event, input_method::Event::Opened) + .then(input_method::Preedit::new); + + shell.request_redraw(); + } + input_method::Event::Preedit(content, selection) => { + let state = state::<Renderer>(tree); + + if state.is_focused.is_some() { + state.preedit = Some(input_method::Preedit { + content: content.to_owned(), + selection: selection.clone(), + text_size: self.size, + }); + + shell.request_redraw(); + } + } + input_method::Event::Commit(text) => { + let state = state::<Renderer>(tree); + + if let Some(focus) = &mut state.is_focused { + let Some(on_input) = &self.on_input else { + return; + }; + + let mut editor = + Editor::new(&mut self.value, &mut state.cursor); + editor.paste(Value::new(text)); + + focus.updated_at = Instant::now(); + state.is_pasting = None; + + let message = (on_input)(editor.contents()); + shell.publish(message); + shell.capture_event(); + + update_cache(state, &self.value); + } + } + }, Event::Window(window::Event::Unfocused) => { let state = state::<Renderer>(tree); @@ -1218,23 +1319,30 @@ where let state = state::<Renderer>(tree); if let Some(focus) = &mut state.is_focused { - if focus.is_window_focused - && matches!( + if focus.is_window_focused { + if matches!( state.cursor.state(&self.value), cursor::State::Index(_) - ) - { - focus.now = *now; - - let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS - - (*now - focus.updated_at).as_millis() - % CURSOR_BLINK_INTERVAL_MILLIS; - - shell.request_redraw_at( - *now + Duration::from_millis( - millis_until_redraw as u64, - ), - ); + ) { + focus.now = *now; + + let millis_until_redraw = + CURSOR_BLINK_INTERVAL_MILLIS + - (*now - focus.updated_at).as_millis() + % CURSOR_BLINK_INTERVAL_MILLIS; + + shell.request_redraw_at( + *now + Duration::from_millis( + millis_until_redraw as u64, + ), + ); + } + + shell.request_input_method(&self.input_method( + state, + layout, + &self.value, + )); } } } @@ -1419,6 +1527,7 @@ pub struct State<P: text::Paragraph> { is_focused: Option<Focus>, is_dragging: bool, is_pasting: Option<Value>, + preedit: Option<input_method::Preedit>, last_click: Option<mouse::Click>, cursor: Cursor, keyboard_modifiers: keyboard::Modifiers, @@ -1431,7 +1540,7 @@ fn state<Renderer: text::Renderer>( tree.state.downcast_mut::<State<Renderer::Paragraph>>() } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] struct Focus { updated_at: Instant, now: Instant, @@ -1614,7 +1723,7 @@ fn replace_paragraph<Renderer>( bounds: Size::new(f32::INFINITY, text_bounds.height), size: text_size, horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, + vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), }); diff --git a/widget/src/themer.rs b/widget/src/themer.rs index 769cc4ca..4e583882 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -113,7 +113,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -220,7 +220,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 56c2be1f..b711432e 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -309,7 +309,7 @@ where fn update( &mut self, _state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index a0ffe392..5bebeeac 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -192,7 +192,7 @@ where fn update( &mut self, tree: &mut widget::Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 2ed9419a..6f878fde 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -249,7 +249,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, @@ -379,7 +379,7 @@ where if state.keyboard_modifiers.control() => { if cursor.is_over(layout.bounds()) { - let delta = match delta { + let delta = match *delta { mouse::ScrollDelta::Lines { x: _, y } => y, mouse::ScrollDelta::Pixels { x: _, y } => y, }; @@ -411,7 +411,7 @@ where } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - state.keyboard_modifiers = modifiers; + state.keyboard_modifiers = *modifiers; } _ => {} } |