From b391b7a23aa24e7754d52169a0e4aa0b80032c32 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Fri, 19 Jul 2024 09:27:49 -0700 Subject: [PATCH 01/15] Update iced --- Cargo.lock | 109 +++++++++++++++----------------- Cargo.toml | 4 +- src/buffer/channel.rs | 7 +- src/buffer/channel/topic.rs | 7 +- src/buffer/file_transfers.rs | 7 +- src/buffer/scroll_view.rs | 7 +- src/screen/dashboard/sidebar.rs | 16 ++--- src/widget/selectable_text.rs | 8 +-- 8 files changed, 76 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c06cc7708..8d9073e7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -416,7 +416,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.66", ] @@ -804,19 +804,21 @@ dependencies = [ [[package]] name = "cosmic-text" -version = "0.10.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75acbfb314aeb4f5210d379af45ed1ec2c98c7f1790bf57b8a4c562ac0c51b71" +checksum = "70b7eecd441fdfc092d6afcb4d00a521ee6d3dc3ad882575ce13bf38be53fb71" dependencies = [ + "bitflags 2.5.0", "fontdb", - "libm", "log", "rangemap", - "rustc-hash", + "rayon", + "rustc-hash 1.1.0", "rustybuzz", "self_cell", "swash", "sys-locale", + "ttf-parser 0.21.1", "unicode-bidi", "unicode-linebreak", "unicode-script", @@ -1357,16 +1359,16 @@ dependencies = [ [[package]] name = "fontdb" -version = "0.15.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020e203f177c0fb250fb19455a252e838d2bbbce1f80f25ecc42402aafa8cd38" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" dependencies = [ "fontconfig-parser", "log", - "memmap2 0.8.0", + "memmap2", "slotmap", "tinyvec", - "ttf-parser 0.19.2", + "ttf-parser 0.20.0", ] [[package]] @@ -1626,12 +1628,12 @@ dependencies = [ [[package]] name = "glyphon" version = "0.5.0" -source = "git+https://github.com/hecrj/glyphon.git?rev=f07e7bab705e69d39a5e6e52c73039a93c4552f8#f07e7bab705e69d39a5e6e52c73039a93c4552f8" +source = "git+https://github.com/hecrj/glyphon.git?rev=feef9f5630c2adb3528937e55f7bfad2da561a65#feef9f5630c2adb3528937e55f7bfad2da561a65" dependencies = [ "cosmic-text", "etagere", "lru", - "rustc-hash", + "rustc-hash 2.0.0", "wgpu", ] @@ -1955,7 +1957,7 @@ dependencies = [ [[package]] name = "iced" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=bdf0430880f5c29443f5f0a0ae4895866dfef4c6#bdf0430880f5c29443f5f0a0ae4895866dfef4c6" +source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" dependencies = [ "iced_core", "iced_futures", @@ -1969,7 +1971,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=bdf0430880f5c29443f5f0a0ae4895866dfef4c6#bdf0430880f5c29443f5f0a0ae4895866dfef4c6" +source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" dependencies = [ "bitflags 2.5.0", "bytes", @@ -1979,7 +1981,7 @@ dependencies = [ "num-traits", "once_cell", "palette", - "rustc-hash", + "rustc-hash 2.0.0", "smol_str", "thiserror", "web-time", @@ -1988,12 +1990,12 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=bdf0430880f5c29443f5f0a0ae4895866dfef4c6#bdf0430880f5c29443f5f0a0ae4895866dfef4c6" +source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" dependencies = [ "futures", "iced_core", "log", - "rustc-hash", + "rustc-hash 2.0.0", "tokio", "wasm-bindgen-futures", "wasm-timer", @@ -2002,7 +2004,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=bdf0430880f5c29443f5f0a0ae4895866dfef4c6#bdf0430880f5c29443f5f0a0ae4895866dfef4c6" +source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" dependencies = [ "bitflags 2.5.0", "bytemuck", @@ -2015,7 +2017,7 @@ dependencies = [ "log", "once_cell", "raw-window-handle", - "rustc-hash", + "rustc-hash 2.0.0", "thiserror", "unicode-segmentation", ] @@ -2023,7 +2025,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=bdf0430880f5c29443f5f0a0ae4895866dfef4c6#bdf0430880f5c29443f5f0a0ae4895866dfef4c6" +source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2035,7 +2037,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=bdf0430880f5c29443f5f0a0ae4895866dfef4c6#bdf0430880f5c29443f5f0a0ae4895866dfef4c6" +source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" dependencies = [ "bytes", "iced_core", @@ -2047,14 +2049,14 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=bdf0430880f5c29443f5f0a0ae4895866dfef4c6#bdf0430880f5c29443f5f0a0ae4895866dfef4c6" +source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" dependencies = [ "bytemuck", "cosmic-text", "iced_graphics", "kurbo", "log", - "rustc-hash", + "rustc-hash 2.0.0", "softbuffer", "tiny-skia", ] @@ -2062,7 +2064,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=bdf0430880f5c29443f5f0a0ae4895866dfef4c6#bdf0430880f5c29443f5f0a0ae4895866dfef4c6" +source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" dependencies = [ "bitflags 2.5.0", "bytemuck", @@ -2073,7 +2075,7 @@ dependencies = [ "iced_graphics", "log", "once_cell", - "rustc-hash", + "rustc-hash 2.0.0", "thiserror", "wgpu", ] @@ -2081,14 +2083,14 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=bdf0430880f5c29443f5f0a0ae4895866dfef4c6#bdf0430880f5c29443f5f0a0ae4895866dfef4c6" +source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" dependencies = [ "iced_renderer", "iced_runtime", "num-traits", "once_cell", "ouroboros", - "rustc-hash", + "rustc-hash 2.0.0", "thiserror", "unicode-segmentation", ] @@ -2096,13 +2098,13 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=bdf0430880f5c29443f5f0a0ae4895866dfef4c6#bdf0430880f5c29443f5f0a0ae4895866dfef4c6" +source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" dependencies = [ "iced_futures", "iced_graphics", "iced_runtime", "log", - "rustc-hash", + "rustc-hash 2.0.0", "thiserror", "tracing", "wasm-bindgen-futures", @@ -2518,15 +2520,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memmap2" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" -dependencies = [ - "libc", -] - [[package]] name = "memmap2" version = "0.9.4" @@ -2618,7 +2611,7 @@ dependencies = [ "indexmap", "log", "num-traits", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "termcolor", "thiserror", @@ -3758,6 +3751,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc_version" version = "0.4.0" @@ -3836,15 +3835,15 @@ dependencies = [ [[package]] name = "rustybuzz" -version = "0.11.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee8fe2a8461a0854a37101fe7a1b13998d0cfa987e43248e81d2a5f4570f6fa" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "bytemuck", "libm", "smallvec", - "ttf-parser 0.20.0", + "ttf-parser 0.21.1", "unicode-bidi-mirroring", "unicode-ccc", "unicode-properties", @@ -3895,7 +3894,7 @@ checksum = "7de61fa7334ee8ee1f5c3c58dcc414fb9361e7e8f5bff9d45f4d69eeb89a7169" dependencies = [ "ab_glyph", "log", - "memmap2 0.9.4", + "memmap2", "smithay-client-toolkit", "tiny-skia", ] @@ -4105,7 +4104,7 @@ dependencies = [ "cursor-icon", "libc", "log", - "memmap2 0.9.4", + "memmap2", "rustix", "thiserror", "wayland-backend", @@ -4163,7 +4162,7 @@ dependencies = [ "foreign-types 0.5.0", "js-sys", "log", - "memmap2 0.9.4", + "memmap2", "objc2", "objc2-app-kit", "objc2-foundation", @@ -4768,12 +4767,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "ttf-parser" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1" - [[package]] name = "ttf-parser" version = "0.20.0" @@ -4811,15 +4804,15 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-bidi-mirroring" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" [[package]] name = "unicode-ccc" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" [[package]] name = "unicode-ident" @@ -5221,7 +5214,7 @@ dependencies = [ "parking_lot 0.12.3", "profiling", "raw-window-handle", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror", "web-sys", @@ -5265,7 +5258,7 @@ dependencies = [ "range-alloc", "raw-window-handle", "renderdoc-sys", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror", "wasm-bindgen", @@ -5662,7 +5655,7 @@ dependencies = [ "dpi", "js-sys", "libc", - "memmap2 0.9.4", + "memmap2", "ndk 0.9.0", "objc2", "objc2-app-kit", diff --git a/Cargo.toml b/Cargo.toml index 7f8164aef..a5359eb94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,5 +52,5 @@ embed-resource = "2.1.1" members = ["data", "ipc", "irc", "irc/proto"] [patch.crates-io] -iced = { git = "https://github.com/iced-rs/iced", rev = "bdf0430880f5c29443f5f0a0ae4895866dfef4c6" } -iced_core = { git = "https://github.com/iced-rs/iced", rev = "bdf0430880f5c29443f5f0a0ae4895866dfef4c6" } +iced = { git = "https://github.com/iced-rs/iced", rev = "c851e67734ec0c761adfd7881c576856ea38734b" } +iced_core = { git = "https://github.com/iced-rs/iced", rev = "c851e67734ec0c761adfd7881c576856ea38734b" } diff --git a/src/buffer/channel.rs b/src/buffer/channel.rs index eb2cd1139..9b9240f50 100644 --- a/src/buffer/channel.rs +++ b/src/buffer/channel.rs @@ -327,10 +327,9 @@ mod nick_list { container( Scrollable::new(column) - .direction(scrollable::Direction::Vertical { - scrollbar: scrollable::Scrollbar::new().width(1).scroller_width(1), - spacing: None, - }) + .direction(scrollable::Direction::Vertical( + scrollable::Scrollbar::new().width(1).scroller_width(1), + )) .style(theme::scrollable::hidden), ) .width(Length::Shrink) diff --git a/src/buffer/channel/topic.rs b/src/buffer/channel/topic.rs index 460efd2ae..be900e0e2 100644 --- a/src/buffer/channel/topic.rs +++ b/src/buffer/channel/topic.rs @@ -53,10 +53,9 @@ pub fn view<'a>( .push_maybe(set_by); let scrollable = Scrollable::new(container(content).width(Length::Fill).padding(padding())) - .direction(scrollable::Direction::Vertical { - scrollbar: scrollable::Scrollbar::new().width(1).scroller_width(1), - spacing: None, - }) + .direction(scrollable::Direction::Vertical( + scrollable::Scrollbar::new().width(1).scroller_width(1), + )) .style(theme::scrollable::hidden); // Use double pass to limit layout to `max_lines` of text diff --git a/src/buffer/file_transfers.rs b/src/buffer/file_transfers.rs index e81403bc3..38c2d1ac4 100644 --- a/src/buffer/file_transfers.rs +++ b/src/buffer/file_transfers.rs @@ -43,10 +43,9 @@ pub fn view<'a>( container( Scrollable::new(column) - .direction(scrollable::Direction::Vertical { - scrollbar: scrollable::Scrollbar::new().width(1).scroller_width(1), - spacing: None, - }) + .direction(scrollable::Direction::Vertical( + scrollable::Scrollbar::new().width(1).scroller_width(1), + )) .style(theme::scrollable::hidden), ) .width(Length::Fill) diff --git a/src/buffer/scroll_view.rs b/src/buffer/scroll_view.rs index d2d50341a..ba2370f6c 100644 --- a/src/buffer/scroll_view.rs +++ b/src/buffer/scroll_view.rs @@ -103,13 +103,12 @@ pub fn view<'a>( }; Scrollable::new(container(content).width(Length::Fill).padding([0, 8])) - .direction(scrollable::Direction::Vertical { - scrollbar: scrollable::Scrollbar::default() + .direction(scrollable::Direction::Vertical( + scrollable::Scrollbar::default() .anchor(status.alignment()) .width(5) .scroller_width(5), - spacing: None, - }) + )) .on_scroll(move |viewport| Message::Scrolled { count, remaining, diff --git a/src/screen/dashboard/sidebar.rs b/src/screen/dashboard/sidebar.rs index 2ed6468ff..b79a1ba05 100644 --- a/src/screen/dashboard/sidebar.rs +++ b/src/screen/dashboard/sidebar.rs @@ -187,15 +187,13 @@ impl Sidebar { menu_buttons = menu_buttons.push(button_with_tooltip); } - let content = - column![ - Scrollable::new(column,).direction(scrollable::Direction::Vertical { - scrollbar: iced::widget::scrollable::Scrollbar::default() - .width(0) - .scroller_width(0), - spacing: None - },), - ]; + let content = column![ + Scrollable::new(column,).direction(scrollable::Direction::Vertical( + iced::widget::scrollable::Scrollbar::default() + .width(0) + .scroller_width(0), + )), + ]; let body = column![container(content).height(Length::Fill), menu_buttons]; diff --git a/src/widget/selectable_text.rs b/src/widget/selectable_text.rs index 80f46a09a..d991a4477 100644 --- a/src/widget/selectable_text.rs +++ b/src/widget/selectable_text.rs @@ -1,5 +1,5 @@ use iced::advanced::renderer::Quad; -use iced::advanced::text::Paragraph; +use iced::advanced::text::{paragraph, Paragraph}; use iced::advanced::widget::{operation, tree, Operation, Tree}; use iced::advanced::{layout, mouse, renderer, text, widget, Layout, Widget}; use iced::widget::text::{Fragment, IntoFragment}; @@ -389,7 +389,7 @@ where if let Some(selection) = state .interaction .selection() - .and_then(|raw| selection(raw, bounds, &state.paragraph, &value)) + .and_then(|raw| selection(raw, bounds, state.paragraph.raw(), &value)) { let content = value.select(selection.start, selection.end).to_string(); operation.custom(&mut (bounds.y, content), None); @@ -423,7 +423,7 @@ fn draw( }; renderer.fill_paragraph( - paragraph, + paragraph.raw(), Point::new(x, y), appearance.color.unwrap_or(style.text_color), *viewport, @@ -443,7 +443,7 @@ where #[derive(Debug, Default)] pub struct State { - paragraph: P, + paragraph: paragraph::Plain

, interaction: Interaction, } From 7262fb089cf18c4d82d49fa1dbfc46eeef91b401 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Fri, 19 Jul 2024 13:42:08 -0700 Subject: [PATCH 02/15] Add rich text & url parsing --- Cargo.lock | 19 +- Cargo.toml | 1 + data/Cargo.toml | 3 +- data/src/client.rs | 2 +- data/src/history/manager.rs | 4 +- data/src/input.rs | 4 +- data/src/message.rs | 239 ++++++++++++--- data/src/message/broadcast.rs | 4 +- src/buffer.rs | 8 +- src/buffer/channel.rs | 19 +- src/buffer/query.rs | 14 +- src/buffer/server.rs | 9 +- src/main.rs | 2 +- src/screen/dashboard.rs | 2 + src/screen/dashboard/pane.rs | 4 +- src/theme/selectable_text.rs | 12 +- src/widget.rs | 31 +- src/widget/selectable_rich_text.rs | 478 +++++++++++++++++++++++++++++ src/widget/selectable_text.rs | 8 +- 19 files changed, 783 insertions(+), 80 deletions(-) create mode 100644 src/widget/selectable_rich_text.rs diff --git a/Cargo.lock b/Cargo.lock index 8d9073e7b..80a305bf6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -410,7 +410,7 @@ dependencies = [ "bitflags 2.5.0", "cexpr", "clang-sys", - "itertools", + "itertools 0.12.1", "lazy_static", "lazycell", "proc-macro2", @@ -965,9 +965,10 @@ dependencies = [ "hex", "iced_core", "irc", - "itertools", + "itertools 0.12.1", "kira", "log", + "once_cell", "palette", "rand", "rand_chacha", @@ -1741,6 +1742,7 @@ dependencies = [ "iced", "image", "ipc", + "itertools 0.13.0", "log", "notify-rust", "once_cell", @@ -2229,7 +2231,7 @@ dependencies = [ name = "irc_proto" version = "0.1.0" dependencies = [ - "itertools", + "itertools 0.12.1", "nom", "thiserror", ] @@ -2271,6 +2273,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -3163,7 +3174,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" dependencies = [ "heck", - "itertools", + "itertools 0.12.1", "proc-macro2", "proc-macro2-diagnostics", "quote", diff --git a/Cargo.toml b/Cargo.toml index a5359eb94..76e2048bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ open = "5.0.1" bytesize = "1.3.0" timeago = "0.4.2" futures = "0.3.30" +itertools = "0.13.0" [dependencies.uuid] version = "1.0" diff --git a/data/Cargo.toml b/data/Cargo.toml index 5b8f3cddd..cd78b362e 100644 --- a/data/Cargo.toml +++ b/data/Cargo.toml @@ -31,10 +31,11 @@ tokio = { version = "1.0", features = ["io-util"] } tokio-stream = { version = "0.1", features = ["time"] } itertools = "0.12.1" timeago = "0.4.2" -url = "2.5.0" +url = { version = "2.5.0", features = ["serde"] } regex = "1.10.4" walkdir = "2.5.0" kira = "0.9.3" +once_cell = "1.19.0" [dependencies.irc] path = "../irc" diff --git a/data/src/client.rs b/data/src/client.rs index 23dea914d..21f385103 100644 --- a/data/src/client.rs +++ b/data/src/client.rs @@ -548,7 +548,7 @@ impl Client { } // Highlight notification - if message::reference_user(user.nickname(), self.nickname(), text) + if message::reference_user_text(user.nickname(), self.nickname(), text) && self.highlight_blackout.allow_highlights() { return Some(vec![Event::Notification( diff --git a/data/src/history/manager.rs b/data/src/history/manager.rs index c8a642173..7344a21bb 100644 --- a/data/src/history/manager.rs +++ b/data/src/history/manager.rs @@ -474,7 +474,9 @@ impl Data { let nick = match source.nick() { Some(nick) => nick.clone(), None => { - if let Some(nickname) = message.text.split(' ').nth(1) { + if let Some(nickname) = + message.plain().and_then(|s| s.split(' ').nth(1)) + { Nick::from(nickname) } else { return true; diff --git a/data/src/input.rs b/data/src/input.rs index 9aeea7e39..a8b186c2e 100644 --- a/data/src/input.rs +++ b/data/src/input.rs @@ -80,14 +80,14 @@ impl Input { server_time: Utc::now(), direction: message::Direction::Sent, target: to_target(target, message::Source::User(user))?, - text, + content: message::parse_fragments(text), }), Command::Me(target, action) => Some(Message { received_at: Posix::now(), server_time: Utc::now(), direction: message::Direction::Sent, target: to_target(target, message::Source::Action)?, - text: message::action_text(user.nickname(), &action), + content: message::action_text(user.nickname(), &action), }), _ => None, } diff --git a/data/src/message.rs b/data/src/message.rs index 08e620722..b8d46c70f 100644 --- a/data/src/message.rs +++ b/data/src/message.rs @@ -1,13 +1,23 @@ use chrono::{DateTime, Utc}; use irc::proto; use irc::proto::Command; +use once_cell::sync::Lazy; +use regex::Regex; use serde::{Deserialize, Serialize}; +use url::Url; pub use self::source::Source; use crate::time::{self, Posix}; use crate::user::{Nick, NickRef}; use crate::{ctcp, Config, User}; +static URL_REGEX: Lazy = Lazy::new(|| { + Regex::new( + r#"(?i)(https?:\/\/)([\da-z\.-]+\.[a-z\.]{2,6}|[\d\.]+)([\/:?=&#]{1}[\da-z\.-]+)*[\/\?]?"#, + ) + .unwrap() +}); + pub type Channel = String; pub(crate) mod broadcast; @@ -76,13 +86,13 @@ pub enum Direction { Received, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] pub struct Message { pub received_at: Posix, pub server_time: DateTime, pub direction: Direction, pub target: Target, - pub text: String, + pub content: Content, } impl Message { @@ -98,7 +108,7 @@ impl Message { resolve_attributes: impl Fn(&User, &str) -> Option, ) -> Option { let server_time = server_time(&encoded); - let text = text(&encoded, &our_nick, config, &resolve_attributes)?; + let content = content(&encoded, &our_nick, config, &resolve_attributes)?; let target = target(encoded, &our_nick, &resolve_attributes)?; Some(Message { @@ -106,7 +116,7 @@ impl Message { server_time, direction: Direction::Received, target, - text, + content, }) } @@ -119,7 +129,7 @@ impl Message { nick: from.clone(), source: Source::Action, }, - text: format!(" ∙ {from} wants to send you \"{filename}\""), + content: plain(format!(" ∙ {from} wants to send you \"{filename}\"")), } } @@ -132,13 +142,112 @@ impl Message { nick: to.clone(), source: Source::Action, }, - text: format!(" ∙ offering to send {to} \"{filename}\""), + content: plain(format!(" ∙ offering to send {to} \"{filename}\"")), } } pub fn with_target(self, target: Target) -> Self { Self { target, ..self } } + + pub fn plain(&self) -> Option<&str> { + match &self.content { + Content::Plain(s) => Some(s), + Content::Fragments(_) => None, + } + } +} + +impl<'de> Deserialize<'de> for Message { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Data { + received_at: Posix, + server_time: DateTime, + direction: Direction, + target: Target, + content: Content, + // Old field before we had fragments + text: Option, + } + + let Data { + received_at, + server_time, + direction, + target, + content, + text, + } = Data::deserialize(deserializer)?; + + let content = if let Some(text) = text { + parse_fragments(text) + } else { + content + }; + + Ok(Message { + received_at, + server_time, + direction, + target, + content, + }) + } +} + +pub fn plain(text: String) -> Content { + Content::Plain(text) +} + +pub fn parse_fragments(text: String) -> Content { + let mut i = 0; + let mut fragments = vec![]; + + for (re_match, url) in URL_REGEX.find_iter(&text).filter_map(|re_match| { + Url::parse(re_match.as_str()) + .ok() + .map(|url| (re_match, url)) + }) { + if i < re_match.start() { + fragments.push(Fragment::Text(text[i..re_match.start()].to_string())); + } + i = re_match.end(); + fragments.push(Fragment::Url(url)); + } + + // No matches, avoid allocation + if i == 0 { + return plain(text); + } else if i < text.len() { + fragments.push(Fragment::Text(text[i..text.len()].to_string())); + } + + Content::Fragments(fragments) +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Content { + Plain(String), + Fragments(Vec), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Fragment { + Text(String), + Url(Url), +} + +impl Fragment { + pub fn as_str(&self) -> &str { + match self { + Fragment::Text(s) => s, + Fragment::Url(u) => u.as_str(), + } + } } fn target( @@ -324,12 +433,12 @@ pub fn server_time(message: &Encoded) -> DateTime { .unwrap_or_else(Utc::now) } -fn text( +fn content( message: &Encoded, our_nick: &Nick, config: &Config, resolve_attributes: &dyn Fn(&User, &str) -> Option, -) -> Option { +) -> Option { use irc::proto::command::Numeric::*; match &message.command { @@ -339,7 +448,7 @@ fn text( let topic = topic.as_ref()?; - Some(format!(" ∙ {user} changed topic to {topic}")) + Some(plain(format!(" ∙ {user} changed topic to {topic}"))) } Command::PART(target, text) => { let raw_user = message.user()?; @@ -352,17 +461,17 @@ fn text( .map(|text| format!(" ({text})")) .unwrap_or_default(); - Some(format!("⟵ {user} has left the channel{text}")) + Some(plain(format!("⟵ {user} has left the channel{text}"))) } Command::JOIN(target, _) => { let raw_user = message.user()?; let user = resolve_attributes(&raw_user, target).unwrap_or(raw_user); (user.nickname() != *our_nick).then(|| { - format!( + plain(format!( "⟶ {} has joined the channel", user.formatted(config.buffer.server_messages.join.username_format) - ) + )) }) } Command::KICK(channel, victim, comment) => { @@ -379,7 +488,7 @@ fn text( format!("{victim} has") }; - Some(format!("⟵ {target} been kicked by {user}{comment}")) + Some(plain(format!("⟵ {target} been kicked by {user}{comment}"))) } Command::MODE(target, modes, args) if proto::is_channel(target) => { let raw_user = message.user()?; @@ -397,7 +506,7 @@ fn text( .collect::>() .join(" "); - Some(format!(" ∙ {user} sets mode {modes} {args}")) + Some(plain(format!(" ∙ {user} sets mode {modes} {args}"))) } Command::PRIVMSG(_, text) => { // Check if a synthetic action message @@ -407,13 +516,13 @@ fn text( } } - Some(text.clone()) + Some(parse_fragments(text.clone())) } - Command::NOTICE(_, text) => Some(text.clone()), + Command::NOTICE(_, text) => Some(parse_fragments(text.clone())), Command::Numeric(RPL_TOPIC, params) => { let topic = params.get(2)?; - Some(format!(" ∙ topic is {topic}")) + Some(plain(format!(" ∙ topic is {topic}"))) } Command::Numeric(RPL_ENDOFWHOIS, _) => { // We skip the end message of a WHOIS. @@ -434,51 +543,53 @@ fn text( let duration = std::time::Duration::from_secs(idle); let idle_readable = formatter.convert(duration); - Some(format!( + Some(plain(format!( " ∙ {nick} signed on at {sign_on_datetime} and has been idle for {idle_readable}" - )) + ))) } Command::Numeric(RPL_WHOISSERVER, params) => { let nick = params.get(1)?; let server = params.get(2)?; let region = params.get(3)?; - Some(format!(" ∙ {nick} is connected on {server} ({region})")) + Some(plain(format!( + " ∙ {nick} is connected on {server} ({region})" + ))) } Command::Numeric(RPL_WHOISUSER, params) => { let nick = params.get(1)?; let userhost = format!("{}@{}", params.get(2)?, params.get(3)?); let real_name = params.get(5)?; - Some(format!( + Some(plain(format!( " ∙ {nick} has userhost {userhost} and real name '{real_name}'" - )) + ))) } Command::Numeric(RPL_WHOISCHANNELS, params) => { let nick = params.get(1)?; let channels = params.get(2)?; - Some(format!(" ∙ {nick} is in {channels}")) + Some(plain(format!(" ∙ {nick} is in {channels}"))) } Command::Numeric(RPL_WHOISACTUALLY, params) => { let nick = params.get(1)?; let ip = params.get(2)?; let status_text = params.get(3)?; - Some(format!(" ∙ {nick} {status_text} {ip}")) + Some(plain(format!(" ∙ {nick} {status_text} {ip}"))) } Command::Numeric(RPL_WHOISSECURE, params) => { let nick = params.get(1)?; let status_text = params.get(2)?; - Some(format!(" ∙ {nick} {status_text}")) + Some(plain(format!(" ∙ {nick} {status_text}"))) } Command::Numeric(RPL_WHOISACCOUNT, params) => { let nick = params.get(1)?; let account = params.get(2)?; let status_text = params.get(3)?; - Some(format!(" ∙ {nick} {status_text} {account}")) + Some(plain(format!(" ∙ {nick} {status_text} {account}"))) } Command::Numeric(RPL_TOPICWHOTIME, params) => { let nick = params.get(2)?; @@ -491,7 +602,7 @@ fn text( .and_then(Posix::datetime)? .to_rfc2822(); - Some(format!(" ∙ topic set by {nick} at {datetime}")) + Some(plain(format!(" ∙ topic set by {nick} at {datetime}"))) } Command::Numeric(RPL_CHANNELMODEIS, params) => { let mode = params @@ -501,7 +612,7 @@ fn text( .collect::>() .join(" "); - Some(format!(" ∙ Channel mode is {mode}")) + Some(plain(format!(" ∙ Channel mode is {mode}"))) } Command::Numeric(RPL_AWAY, params) => { let user = params.get(1)?; @@ -510,16 +621,16 @@ fn text( .map(|away| format!(" ({away})")) .unwrap_or_default(); - Some(format!(" ∙ {user} is away{away_message}")) + Some(plain(format!(" ∙ {user} is away{away_message}"))) } - Command::Numeric(_, responses) | Command::Unknown(_, responses) => Some( + Command::Numeric(_, responses) | Command::Unknown(_, responses) => Some(parse_fragments( responses .iter() .map(|s| s.as_str()) .skip(1) .collect::>() .join(" "), - ), + )), _ => None, } } @@ -552,16 +663,74 @@ pub fn is_action(text: &str) -> bool { } } -pub fn parse_action(nick: NickRef, text: &str) -> Option { +fn parse_action(nick: NickRef, text: &str) -> Option { let query = ctcp::parse_query(text)?; Some(action_text(nick, query.params)) } -pub fn action_text(nick: NickRef, action: &str) -> String { - format!(" ∙ {nick} {action}") +pub fn action_text(nick: NickRef, action: &str) -> Content { + plain(format!(" ∙ {nick} {action}")) } -pub fn reference_user(sender: NickRef, own_nick: NickRef, text: &str) -> bool { +pub fn reference_user(sender: NickRef, own_nick: NickRef, message: &Message) -> bool { + let has_nick = match &message.content { + Content::Plain(text) => text.contains(own_nick.as_ref()), + Content::Fragments(fragments) => fragments + .iter() + .any(|f| f.as_str().contains(own_nick.as_ref())), + }; + + sender != own_nick && has_nick +} + +pub fn reference_user_text(sender: NickRef, own_nick: NickRef, text: &str) -> bool { sender != own_nick && text.contains(own_nick.as_ref()) } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn fragment_parsing() { + let tests = [ + ( + "Checkout https://foo.bar/asdf?1=2 now!", + vec![ + Fragment::Text("Checkout ".into()), + Fragment::Url("https://foo.bar/asdf?1=2".parse().unwrap()), + Fragment::Text(" now!".into()), + ], + ), + ( + "http://google.us.edi?34535/534534?dfg=g&fg", + vec![Fragment::Url( + "http://google.us.edi?34535/534534?dfg=g&fg" + .parse() + .unwrap(), + )], + ), + ( + "http://regexr.com is a great tool", + vec![ + Fragment::Url("http://regexr.com".parse().unwrap()), + Fragment::Text(" is a great tool".into()), + ], + ), + ( + "We have a wiki at https://halloy.squidowl.org", + vec![ + Fragment::Text("We have a wiki at ".into()), + Fragment::Url("https://halloy.squidowl.org".parse().unwrap()), + ], + ), + ]; + + for (text, expected) in tests { + let actual = parse_fragments(text.to_string()); + + assert_eq!(Content::Fragments(expected), actual); + } + } +} diff --git a/data/src/message/broadcast.rs b/data/src/message/broadcast.rs index 8a8d6c35e..229ca5b64 100644 --- a/data/src/message/broadcast.rs +++ b/data/src/message/broadcast.rs @@ -1,7 +1,7 @@ //! Generate messages that can be broadcast into every buffer use chrono::{DateTime, Utc}; -use super::{source, Direction, Message, Source, Target}; +use super::{plain, source, Direction, Message, Source, Target}; use crate::time::Posix; use crate::user::Nick; use crate::{Config, User}; @@ -25,7 +25,7 @@ fn expand( server_time: sent_time, direction: Direction::Received, target, - text, + content: plain(text), } }; diff --git a/src/buffer.rs b/src/buffer.rs index 6413fb603..c0c873855 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -8,6 +8,7 @@ use self::file_transfers::FileTransfers; use self::query::Query; use self::server::Server; use crate::widget::Element; +use crate::Theme; pub mod channel; pub mod empty; @@ -103,6 +104,7 @@ impl Buffer { history: &'a history::Manager, settings: &'a buffer::Settings, config: &'a Config, + theme: &'a Theme, is_focused: bool, ) -> Element<'a, Message> { match self { @@ -113,14 +115,16 @@ impl Buffer { history, &settings.channel, config, + theme, is_focused, ) .map(Message::Channel), Buffer::Server(state) => { - server::view(state, clients, history, config, is_focused).map(Message::Server) + server::view(state, clients, history, config, theme, is_focused) + .map(Message::Server) } Buffer::Query(state) => { - query::view(state, clients, history, config, is_focused).map(Message::Query) + query::view(state, clients, history, config, theme, is_focused).map(Message::Query) } Buffer::FileTransfers(state) => { file_transfers::view(state, file_transfers).map(Message::FileTransfers) diff --git a/src/buffer/channel.rs b/src/buffer/channel.rs index 9b9240f50..4fbbdc4b3 100644 --- a/src/buffer/channel.rs +++ b/src/buffer/channel.rs @@ -6,8 +6,8 @@ use iced::widget::{column, container, row}; use iced::{padding, Length, Task}; use super::{input_view, scroll_view, user_context}; -use crate::theme; -use crate::widget::{selectable_text, Element}; +use crate::widget::{message_content, selectable_text, Element}; +use crate::{theme, Theme}; mod topic; @@ -29,6 +29,7 @@ pub fn view<'a>( history: &'a history::Manager, settings: &'a channel::Settings, config: &'a Config, + theme: &'a Theme, is_focused: bool, ) -> Element<'a, Message> { let buffer = state.buffer(); @@ -79,7 +80,7 @@ pub fn view<'a>( .map(scroll_view::Message::UserContext); let space = selectable_text(" "); - let text = selectable_text(&message.text); + let text = message_content(message, theme, theme::selectable_text::default); Some( container( @@ -91,11 +92,7 @@ pub fn view<'a>( ) .style(move |theme| match our_nick { Some(nick) - if message::reference_user( - user.nickname(), - nick, - &message.text, - ) => + if message::reference_user(user.nickname(), nick, message) => { theme::container::highlight(theme) } @@ -105,7 +102,7 @@ pub fn view<'a>( ) } message::Source::Server(server) => { - let message = selectable_text(&message.text).style(move |theme| { + let message = message_content(message, theme, move |theme| { theme::selectable_text::server( theme, server.as_ref(), @@ -117,12 +114,12 @@ pub fn view<'a>( } message::Source::Action => { let message = - selectable_text(&message.text).style(theme::selectable_text::accent); + message_content(message, theme, theme::selectable_text::accent); Some(container(row![].push_maybe(timestamp).push(message)).into()) } message::Source::Internal(message::source::Internal::Status(status)) => { - let message = selectable_text(&message.text).style(move |theme| { + let message = message_content(message, theme, move |theme| { theme::selectable_text::status( theme, *status, diff --git a/src/buffer/query.rs b/src/buffer/query.rs index 58a363638..22dde1195 100644 --- a/src/buffer/query.rs +++ b/src/buffer/query.rs @@ -4,8 +4,8 @@ use iced::widget::{column, container, row, vertical_space}; use iced::{Length, Task}; use super::{input_view, scroll_view, user_context}; -use crate::theme; -use crate::widget::{selectable_text, Element}; +use crate::widget::{message_content, selectable_text, Element}; +use crate::{theme, Theme}; #[derive(Debug, Clone)] pub enum Message { @@ -23,6 +23,7 @@ pub fn view<'a>( clients: &'a data::client::Map, history: &'a history::Manager, config: &'a Config, + theme: &'a Theme, is_focused: bool, ) -> Element<'a, Message> { let status = clients.status(&state.server); @@ -67,7 +68,8 @@ pub fn view<'a>( .map(scroll_view::Message::UserContext); let space = selectable_text(" "); - let message = selectable_text(&message.text); + let message = + message_content(message, theme, theme::selectable_text::default); Some( container( @@ -81,7 +83,7 @@ pub fn view<'a>( ) } message::Source::Server(server) => { - let message = selectable_text(&message.text).style(move |theme| { + let message = message_content(message, theme, move |theme| { theme::selectable_text::server( theme, server.as_ref(), @@ -93,12 +95,12 @@ pub fn view<'a>( } message::Source::Action => { let message = - selectable_text(&message.text).style(theme::selectable_text::accent); + message_content(message, theme, theme::selectable_text::accent); Some(container(row![].push_maybe(timestamp).push(message)).into()) } message::Source::Internal(message::source::Internal::Status(status)) => { - let message = selectable_text(&message.text).style(move |theme| { + let message = message_content(message, theme, move |theme| { theme::selectable_text::status( theme, *status, diff --git a/src/buffer/server.rs b/src/buffer/server.rs index a968513c5..58fb926bc 100644 --- a/src/buffer/server.rs +++ b/src/buffer/server.rs @@ -3,8 +3,8 @@ use iced::widget::{column, container, row, vertical_space}; use iced::{Length, Task}; use super::{input_view, scroll_view}; -use crate::theme; -use crate::widget::{selectable_text, Element}; +use crate::widget::{message_content, selectable_text, Element}; +use crate::{theme, Theme}; #[derive(Debug, Clone)] pub enum Message { @@ -17,6 +17,7 @@ pub fn view<'a>( clients: &'a data::client::Map, history: &'a history::Manager, config: &'a Config, + theme: &'a Theme, is_focused: bool, ) -> Element<'a, Message> { let status = clients.status(&state.server); @@ -40,7 +41,7 @@ pub fn view<'a>( match message.target.source() { message::Source::Server(server) => { - let message = selectable_text(&message.text).style(move |theme| { + let message = message_content(message, theme, move |theme| { theme::selectable_text::server( theme, server.as_ref(), @@ -51,7 +52,7 @@ pub fn view<'a>( Some(container(row![].push_maybe(timestamp).push(message)).into()) } message::Source::Internal(message::source::Internal::Status(status)) => { - let message = selectable_text(&message.text).style(move |theme| { + let message = message_content(message, theme, move |theme| { theme::selectable_text::status( theme, *status, diff --git a/src/main.rs b/src/main.rs index 5dbca1194..ade20ece9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -677,7 +677,7 @@ impl Halloy { fn view(&self) -> Element { let screen = match &self.screen { Screen::Dashboard(dashboard) => dashboard - .view(&self.clients, &self.version, &self.config) + .view(&self.clients, &self.version, &self.config, &self.theme) .map(Message::Dashboard), Screen::Help(help) => help.view().map(Message::Help), Screen::Welcome(welcome) => welcome.view().map(Message::Welcome), diff --git a/src/screen/dashboard.rs b/src/screen/dashboard.rs index e635ac2c9..71dc8a988 100644 --- a/src/screen/dashboard.rs +++ b/src/screen/dashboard.rs @@ -585,6 +585,7 @@ impl Dashboard { clients: &'a client::Map, version: &'a Version, config: &'a Config, + theme: &'a Theme, ) -> Element<'a, Message> { let focus = self.focus; @@ -600,6 +601,7 @@ impl Dashboard { &self.file_transfers, &self.history, config, + theme, ) }) .on_click(pane::Message::PaneClicked) diff --git a/src/screen/dashboard/pane.rs b/src/screen/dashboard/pane.rs index dfd717461..bd0ac4af9 100644 --- a/src/screen/dashboard/pane.rs +++ b/src/screen/dashboard/pane.rs @@ -4,7 +4,7 @@ use uuid::Uuid; use crate::buffer::{self, Buffer}; use crate::widget::tooltip; -use crate::{icon, theme, widget}; +use crate::{icon, theme, widget, Theme}; #[derive(Debug, Clone)] pub enum Message { @@ -54,6 +54,7 @@ impl Pane { file_transfers: &'a file_transfer::Manager, history: &'a history::Manager, config: &'a Config, + theme: &'a Theme, ) -> widget::Content<'a, Message> { let title_bar_text = match &self.buffer { Buffer::Empty => "".to_string(), @@ -96,6 +97,7 @@ impl Pane { history, &self.settings, config, + theme, is_focused, ) .map(move |msg| Message::Buffer(id, msg)); diff --git a/src/theme/selectable_text.rs b/src/theme/selectable_text.rs index 758b8fbae..a6720b84c 100644 --- a/src/theme/selectable_text.rs +++ b/src/theme/selectable_text.rs @@ -8,10 +8,7 @@ impl Catalog for Theme { type Class<'a> = StyleFn<'a, Self>; fn default<'a>() -> Self::Class<'a> { - Box::new(|theme| Style { - color: None, - selection_color: theme.colors().accent.high_alpha, - }) + Box::new(default) } fn style(&self, class: &Self::Class<'_>) -> Style { @@ -19,6 +16,13 @@ impl Catalog for Theme { } } +pub fn default(theme: &Theme) -> Style { + Style { + color: None, + selection_color: theme.colors().accent.high_alpha, + } +} + pub fn transparent(theme: &Theme) -> Style { let color = text::transparent(theme).color; diff --git a/src/widget.rs b/src/widget.rs index a62069e3d..c8604ef51 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -1,14 +1,19 @@ #![allow(dead_code)] +use data::Message; +use iced::widget::span; + +use crate::Theme; + pub use self::anchored_overlay::anchored_overlay; pub use self::combo_box::combo_box; pub use self::context_menu::context_menu; pub use self::double_pass::double_pass; pub use self::key_press::key_press; pub use self::modal::modal; +pub use self::selectable_rich_text::selectable_rich_text; pub use self::selectable_text::selectable_text; pub use self::shortcut::shortcut; pub use self::tooltip::tooltip; -use crate::Theme; pub mod anchored_overlay; pub mod collection; @@ -19,6 +24,7 @@ pub mod double_pass; pub mod hover; pub mod key_press; pub mod modal; +pub mod selectable_rich_text; pub mod selectable_text; pub mod shortcut; pub mod tooltip; @@ -32,3 +38,26 @@ pub type Row<'a, Message> = iced::widget::Row<'a, Message, Theme, Renderer>; pub type Text<'a> = iced::widget::Text<'a, Theme, Renderer>; pub type Container<'a, Message> = iced::widget::Container<'a, Message, Theme, Renderer>; pub type Button<'a, Message> = iced::widget::Button<'a, Message, Theme>; + +pub fn message_content<'a, M>( + message: &'a Message, + theme: &'a Theme, + style: impl Fn(&Theme) -> selectable_text::Style + 'a, +) -> Element<'a, M> { + match &message.content { + data::message::Content::Plain(text) => selectable_text(text).style(style).into(), + data::message::Content::Fragments(fragments) => selectable_rich_text( + fragments + .iter() + .map(|fragment| match fragment { + data::message::Fragment::Text(s) => span(s), + data::message::Fragment::Url(s) => span(s.as_str()) + // .font(font::MONO_BOLD.clone()) + .color(theme.colors().action.base), + }) + .collect::>(), + ) + .style(style) + .into(), + } +} diff --git a/src/widget/selectable_rich_text.rs b/src/widget/selectable_rich_text.rs new file mode 100644 index 000000000..f76b6a3de --- /dev/null +++ b/src/widget/selectable_rich_text.rs @@ -0,0 +1,478 @@ +use std::borrow::Cow; + +use iced::advanced::layout::{self, Layout}; +use iced::advanced::renderer::{self, Quad}; +use iced::advanced::text::{self, Paragraph, Span, Text}; +use iced::advanced::widget::tree::{self, Tree}; +use iced::advanced::widget::Operation; +use iced::advanced::Widget; +use iced::widget::text::{LineHeight, Shaping}; +use iced::widget::text_input::Value; +use iced::{alignment, event, Border, Shadow}; +use iced::{mouse, touch}; +use iced::{widget, Point}; +use iced::{Color, Element, Length, Pixels, Rectangle, Size}; +use itertools::Itertools; + +use super::selectable_text::{selection, Catalog, Interaction, Style, StyleFn}; + +/// Creates a new [`Rich`] text widget with the provided spans. +pub fn selectable_rich_text<'a, Theme, Renderer>( + spans: impl Into]>>, +) -> Rich<'a, Theme, Renderer> +where + Theme: Catalog + 'a, + Renderer: text::Renderer, +{ + Rich::with_spans(spans) +} + +/// A bunch of [`Rich`] text. +#[derive(Debug)] +pub struct Rich<'a, Theme = iced::Theme, Renderer = iced::Renderer> +where + Theme: Catalog, + Renderer: text::Renderer, +{ + spans: Cow<'a, [Span<'a, Renderer::Font>]>, + size: Option, + line_height: LineHeight, + width: Length, + height: Length, + font: Option, + align_x: alignment::Horizontal, + align_y: alignment::Vertical, + class: Theme::Class<'a>, +} + +impl<'a, Theme, Renderer> Rich<'a, Theme, Renderer> +where + Theme: Catalog, + Renderer: text::Renderer, +{ + /// Creates a new empty [`Rich`] text. + pub fn new() -> Self { + Self { + spans: Cow::default(), + size: None, + line_height: LineHeight::default(), + width: Length::Shrink, + height: Length::Shrink, + font: None, + align_x: alignment::Horizontal::Left, + align_y: alignment::Vertical::Top, + class: Theme::default(), + } + } + + /// Creates a new [`Rich`] text with the given text spans. + pub fn with_spans(spans: impl Into]>>) -> Self { + Self { + spans: spans.into(), + ..Self::new() + } + } + + /// Sets the default size of the [`Rich`] text. + pub fn size(mut self, size: impl Into) -> Self { + self.size = Some(size.into()); + self + } + + /// Sets the defualt [`LineHeight`] of the [`Rich`] text. + pub fn line_height(mut self, line_height: impl Into) -> Self { + self.line_height = line_height.into(); + self + } + + /// Sets the default font of the [`Rich`] text. + pub fn font(mut self, font: impl Into) -> Self { + self.font = Some(font.into()); + self + } + + /// Sets the width of the [`Rich`] text boundaries. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Sets the height of the [`Rich`] text boundaries. + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + + /// Centers the [`Rich`] text, both horizontally and vertically. + pub fn center(self) -> Self { + self.align_x(alignment::Horizontal::Center) + .align_y(alignment::Vertical::Center) + } + + /// Sets the [`alignment::Horizontal`] of the [`Rich`] text. + pub fn align_x(mut self, alignment: impl Into) -> Self { + self.align_x = alignment.into(); + self + } + + /// Sets the [`alignment::Vertical`] of the [`Rich`] text. + pub fn align_y(mut self, alignment: impl Into) -> Self { + self.align_y = alignment.into(); + self + } + + /// Sets the default style of the [`Rich`] text. + #[must_use] + pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self + where + Theme::Class<'a>: From>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the default [`Color`] of the [`Rich`] text. + pub fn color(self, color: impl Into) -> Self + where + Theme::Class<'a>: From>, + { + self.color_maybe(Some(color)) + } + + /// Sets the default [`Color`] of the [`Rich`] text, if `Some`. + pub fn color_maybe(self, color: Option>) -> Self + where + Theme::Class<'a>: From>, + { + let color = color.map(Into::into); + + self.style(move |_theme| Style { + color, + selection_color: Color::WHITE, + }) + } + + /// Sets the default style class of the [`Rich`] text. + #[must_use] + pub fn class(mut self, class: impl Into>) -> Self { + self.class = class.into(); + self + } + + /// Adds a new text [`Span`] to the [`Rich`] text. + pub fn push(mut self, span: impl Into>) -> Self { + self.spans.to_mut().push(span.into()); + self + } +} + +impl<'a, Theme, Renderer> Default for Rich<'a, Theme, Renderer> +where + Theme: Catalog, + Renderer: text::Renderer, +{ + fn default() -> Self { + Self::new() + } +} + +struct State { + spans: Vec>, + paragraph: P, + interaction: Interaction, +} + +impl<'a, Message, Theme, Renderer> Widget for Rich<'a, Theme, Renderer> +where + Theme: Catalog, + Renderer: text::Renderer, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(State { + spans: Vec::new(), + paragraph: Renderer::Paragraph::default(), + interaction: Interaction::default(), + }) + } + + fn size(&self) -> Size { + Size { + width: self.width, + height: self.height, + } + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + layout( + tree.state.downcast_mut::>(), + renderer, + limits, + self.width, + self.height, + self.spans.as_ref(), + self.line_height, + self.size, + self.font, + self.align_x, + self.align_y, + ) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: iced::Event, + _layout: Layout<'_>, + cursor: mouse::Cursor, + _renderer: &Renderer, + _clipboard: &mut dyn iced::advanced::Clipboard, + _shell: &mut iced::advanced::Shell<'_, Message>, + _viewport: &Rectangle, + ) -> event::Status { + let state = tree.state.downcast_mut::>(); + + match event { + iced::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | iced::Event::Touch(touch::Event::FingerPressed { .. }) => { + if let Some(cursor) = cursor.position() { + state.interaction = Interaction::Selecting(selection::Raw { + start: cursor, + end: cursor, + }); + } else { + state.interaction = Interaction::Idle; + } + } + iced::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | iced::Event::Touch(touch::Event::FingerLifted { .. }) + | iced::Event::Touch(touch::Event::FingerLost { .. }) => { + if let Interaction::Selecting(raw) = state.interaction { + state.interaction = Interaction::Selected(raw); + } else { + state.interaction = Interaction::Idle; + } + } + iced::Event::Mouse(mouse::Event::CursorMoved { .. }) + | iced::Event::Touch(touch::Event::FingerMoved { .. }) => { + if let Some(cursor) = cursor.position() { + if let Interaction::Selecting(raw) = &mut state.interaction { + raw.end = cursor; + } + } + } + _ => {} + } + + event::Status::Ignored + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + defaults: &renderer::Style, + layout: Layout<'_>, + _cursor_position: mouse::Cursor, + viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + + if viewport.intersection(&bounds).is_none() { + return; + } + + let state = tree.state.downcast_ref::>(); + let style = theme.style(&self.class); + + if let Some(selection) = state + .interaction + .selection() + .and_then(|raw| raw.resolve(bounds)) + { + let line_height = f32::from( + self.line_height.to_absolute( + self.size + .map(Pixels::from) + .unwrap_or_else(|| renderer.default_size()), + ), + ); + + let baseline_y = + bounds.y + ((selection.start.y - bounds.y) / line_height).floor() * line_height; + + let height = selection.end.y - baseline_y - 0.5; + let rows = (height / line_height).ceil() as usize; + + for row in 0..rows { + let (x, width) = if row == 0 { + ( + selection.start.x, + if rows == 1 { + f32::min(selection.end.x, bounds.x + bounds.width) - selection.start.x + } else { + bounds.x + bounds.width - selection.start.x + }, + ) + } else if row == rows - 1 { + (bounds.x, selection.end.x - bounds.x) + } else { + (bounds.x, bounds.width) + }; + let y = baseline_y + row as f32 * line_height; + + renderer.fill_quad( + Quad { + bounds: Rectangle::new(Point::new(x, y), Size::new(width, line_height)), + border: Border { + radius: 0.0.into(), + width: 0.0, + color: Color::TRANSPARENT, + }, + shadow: Shadow::default(), + }, + style.selection_color, + ); + } + } + + widget::text::draw( + renderer, + defaults, + layout, + &state.paragraph, + widget::text::Style { color: style.color }, + viewport, + ); + } + + fn mouse_interaction( + &self, + _state: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + if cursor.position_over(layout.bounds()).is_some() { + mouse::Interaction::Text + } else { + mouse::Interaction::default() + } + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + _renderer: &Renderer, + operation: &mut dyn Operation<()>, + ) { + let state = tree.state.downcast_ref::>(); + + let bounds = layout.bounds(); + let value = Value::new(&self.spans.iter().map(|s| s.text.as_ref()).join("")); + if let Some(selection) = state + .interaction + .selection() + .and_then(|raw| selection(raw, bounds, &state.paragraph, &value)) + { + let content = value.select(selection.start, selection.end).to_string(); + operation.custom(&mut (bounds.y, content), None); + } + } +} + +fn layout( + state: &mut State, + renderer: &Renderer, + limits: &layout::Limits, + width: Length, + height: Length, + spans: &[Span<'_, Renderer::Font>], + line_height: LineHeight, + size: Option, + font: Option, + horizontal_alignment: alignment::Horizontal, + vertical_alignment: alignment::Vertical, +) -> layout::Node +where + Renderer: text::Renderer, +{ + layout::sized(limits, width, height, |limits| { + let bounds = limits.max(); + + let size = size.unwrap_or_else(|| renderer.default_size()); + let font = font.unwrap_or_else(|| renderer.default_font()); + + let text_with_spans = || Text { + content: spans, + bounds, + size, + line_height, + font, + horizontal_alignment, + vertical_alignment, + shaping: Shaping::Advanced, + }; + + if state.spans != spans { + state.paragraph = Renderer::Paragraph::with_spans(text_with_spans()); + state.spans = spans.iter().cloned().map(Span::to_static).collect(); + } else { + match state.paragraph.compare(Text { + content: (), + bounds, + size, + line_height, + font, + horizontal_alignment, + vertical_alignment, + shaping: Shaping::Advanced, + }) { + text::Difference::None => {} + text::Difference::Bounds => { + state.paragraph.resize(bounds); + } + text::Difference::Shape => { + state.paragraph = Renderer::Paragraph::with_spans(text_with_spans()); + } + } + } + + state.paragraph.min_bounds() + }) +} + +impl<'a, Theme, Renderer> FromIterator> for Rich<'a, Theme, Renderer> +where + Theme: Catalog, + Renderer: text::Renderer, +{ + fn from_iter>>(spans: T) -> Self { + Self { + spans: spans.into_iter().collect(), + ..Self::new() + } + } +} + +impl<'a, Message, Theme, Renderer> From> + for Element<'a, Message, Theme, Renderer> +where + Theme: Catalog + 'a, + Renderer: text::Renderer + 'a, +{ + fn from(text: Rich<'a, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> { + Element::new(text) + } +} diff --git a/src/widget/selectable_text.rs b/src/widget/selectable_text.rs index d991a4477..c72d557af 100644 --- a/src/widget/selectable_text.rs +++ b/src/widget/selectable_text.rs @@ -9,10 +9,10 @@ use iced::{ Size, Task, }; -use self::selection::selection; +pub use self::selection::selection; pub use self::text::{LineHeight, Shaping}; -mod selection; +pub mod selection; pub fn selectable_text<'a, Theme, Renderer>( fragment: impl IntoFragment<'a>, @@ -448,7 +448,7 @@ pub struct State { } #[derive(Debug, Clone, Copy, Default)] -enum Interaction { +pub enum Interaction { #[default] Idle, Selecting(selection::Raw), @@ -456,7 +456,7 @@ enum Interaction { } impl Interaction { - fn selection(self) -> Option { + pub fn selection(self) -> Option { match &self { Interaction::Idle => None, Interaction::Selecting(raw) | Interaction::Selected(raw) => Some(*raw), From dfb7733476f6981666217128d68e4ccba487fa6f Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Fri, 19 Jul 2024 14:55:55 -0700 Subject: [PATCH 03/15] Add link detection / opening --- src/buffer/channel.rs | 53 ++++++--- src/buffer/query.rs | 54 ++++++--- src/buffer/scroll_view.rs | 4 + src/buffer/server.rs | 38 +++--- src/widget.rs | 16 ++- src/widget/selectable_rich_text.rs | 152 ++++++++++++++++++++---- src/widget/selectable_text/selection.rs | 2 +- 7 files changed, 240 insertions(+), 79 deletions(-) diff --git a/src/buffer/channel.rs b/src/buffer/channel.rs index 4fbbdc4b3..1d1c2e109 100644 --- a/src/buffer/channel.rs +++ b/src/buffer/channel.rs @@ -80,7 +80,12 @@ pub fn view<'a>( .map(scroll_view::Message::UserContext); let space = selectable_text(" "); - let text = message_content(message, theme, theme::selectable_text::default); + let text = message_content( + message, + theme, + scroll_view::Message::Link, + theme::selectable_text::default, + ); Some( container( @@ -102,30 +107,44 @@ pub fn view<'a>( ) } message::Source::Server(server) => { - let message = message_content(message, theme, move |theme| { - theme::selectable_text::server( - theme, - server.as_ref(), - &config.buffer.server_messages, - ) - }); + let message = message_content( + message, + theme, + scroll_view::Message::Link, + move |theme| { + theme::selectable_text::server( + theme, + server.as_ref(), + &config.buffer.server_messages, + ) + }, + ); Some(container(row![].push_maybe(timestamp).push(message)).into()) } message::Source::Action => { - let message = - message_content(message, theme, theme::selectable_text::accent); + let message = message_content( + message, + theme, + scroll_view::Message::Link, + theme::selectable_text::accent, + ); Some(container(row![].push_maybe(timestamp).push(message)).into()) } message::Source::Internal(message::source::Internal::Status(status)) => { - let message = message_content(message, theme, move |theme| { - theme::selectable_text::status( - theme, - *status, - &config.buffer.internal_messages, - ) - }); + let message = message_content( + message, + theme, + scroll_view::Message::Link, + move |theme| { + theme::selectable_text::status( + theme, + *status, + &config.buffer.internal_messages, + ) + }, + ); Some(container(row![].push_maybe(timestamp).push(message)).into()) } diff --git a/src/buffer/query.rs b/src/buffer/query.rs index 22dde1195..60bb25255 100644 --- a/src/buffer/query.rs +++ b/src/buffer/query.rs @@ -68,8 +68,12 @@ pub fn view<'a>( .map(scroll_view::Message::UserContext); let space = selectable_text(" "); - let message = - message_content(message, theme, theme::selectable_text::default); + let message = message_content( + message, + theme, + scroll_view::Message::Link, + theme::selectable_text::default, + ); Some( container( @@ -83,30 +87,44 @@ pub fn view<'a>( ) } message::Source::Server(server) => { - let message = message_content(message, theme, move |theme| { - theme::selectable_text::server( - theme, - server.as_ref(), - &config.buffer.server_messages, - ) - }); + let message = message_content( + message, + theme, + scroll_view::Message::Link, + move |theme| { + theme::selectable_text::server( + theme, + server.as_ref(), + &config.buffer.server_messages, + ) + }, + ); Some(container(row![].push_maybe(timestamp).push(message)).into()) } message::Source::Action => { - let message = - message_content(message, theme, theme::selectable_text::accent); + let message = message_content( + message, + theme, + scroll_view::Message::Link, + theme::selectable_text::accent, + ); Some(container(row![].push_maybe(timestamp).push(message)).into()) } message::Source::Internal(message::source::Internal::Status(status)) => { - let message = message_content(message, theme, move |theme| { - theme::selectable_text::status( - theme, - *status, - &config.buffer.internal_messages, - ) - }); + let message = message_content( + message, + theme, + scroll_view::Message::Link, + move |theme| { + theme::selectable_text::status( + theme, + *status, + &config.buffer.internal_messages, + ) + }, + ); Some(container(row![].push_maybe(timestamp).push(message)).into()) } diff --git a/src/buffer/scroll_view.rs b/src/buffer/scroll_view.rs index ba2370f6c..45a23fcc9 100644 --- a/src/buffer/scroll_view.rs +++ b/src/buffer/scroll_view.rs @@ -19,6 +19,7 @@ pub enum Message { viewport: scrollable::Viewport, }, UserContext(user_context::Message), + Link(String), } #[derive(Debug, Clone)] @@ -215,6 +216,9 @@ impl State { Some(Event::UserContext(user_context::update(message))), ); } + Message::Link(link) => { + let _ = open::that(link); + } } (Task::none(), None) diff --git a/src/buffer/server.rs b/src/buffer/server.rs index 58fb926bc..74df21c4f 100644 --- a/src/buffer/server.rs +++ b/src/buffer/server.rs @@ -41,24 +41,34 @@ pub fn view<'a>( match message.target.source() { message::Source::Server(server) => { - let message = message_content(message, theme, move |theme| { - theme::selectable_text::server( - theme, - server.as_ref(), - &config.buffer.server_messages, - ) - }); + let message = message_content( + message, + theme, + scroll_view::Message::Link, + move |theme| { + theme::selectable_text::server( + theme, + server.as_ref(), + &config.buffer.server_messages, + ) + }, + ); Some(container(row![].push_maybe(timestamp).push(message)).into()) } message::Source::Internal(message::source::Internal::Status(status)) => { - let message = message_content(message, theme, move |theme| { - theme::selectable_text::status( - theme, - *status, - &config.buffer.internal_messages, - ) - }); + let message = message_content( + message, + theme, + scroll_view::Message::Link, + move |theme| { + theme::selectable_text::status( + theme, + *status, + &config.buffer.internal_messages, + ) + }, + ); Some(container(row![].push_maybe(timestamp).push(message)).into()) } diff --git a/src/widget.rs b/src/widget.rs index c8604ef51..1cf895b88 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -39,9 +39,10 @@ pub type Text<'a> = iced::widget::Text<'a, Theme, Renderer>; pub type Container<'a, Message> = iced::widget::Container<'a, Message, Theme, Renderer>; pub type Button<'a, Message> = iced::widget::Button<'a, Message, Theme>; -pub fn message_content<'a, M>( +pub fn message_content<'a, M: 'a>( message: &'a Message, theme: &'a Theme, + on_link_pressed: impl Fn(String) -> M + 'a, style: impl Fn(&Theme) -> selectable_text::Style + 'a, ) -> Element<'a, M> { match &message.content { @@ -50,13 +51,18 @@ pub fn message_content<'a, M>( fragments .iter() .map(|fragment| match fragment { - data::message::Fragment::Text(s) => span(s), - data::message::Fragment::Url(s) => span(s.as_str()) - // .font(font::MONO_BOLD.clone()) - .color(theme.colors().action.base), + data::message::Fragment::Text(s) => { + selectable_rich_text::CustomSpan::Span(span(s)) + } + data::message::Fragment::Url(s) => selectable_rich_text::CustomSpan::Link( + span(s.as_str()) + // .font(font::MONO_BOLD.clone()) + .color(theme.colors().action.base), + ), }) .collect::>(), ) + .on_link_pressed(on_link_pressed) .style(style) .into(), } diff --git a/src/widget/selectable_rich_text.rs b/src/widget/selectable_rich_text.rs index f76b6a3de..967d90557 100644 --- a/src/widget/selectable_rich_text.rs +++ b/src/widget/selectable_rich_text.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::ops::Deref; use iced::advanced::layout::{self, Layout}; use iced::advanced::renderer::{self, Quad}; @@ -13,13 +14,14 @@ use iced::{mouse, touch}; use iced::{widget, Point}; use iced::{Color, Element, Length, Pixels, Rectangle, Size}; use itertools::Itertools; +use unicode_segmentation::UnicodeSegmentation; use super::selectable_text::{selection, Catalog, Interaction, Style, StyleFn}; /// Creates a new [`Rich`] text widget with the provided spans. -pub fn selectable_rich_text<'a, Theme, Renderer>( - spans: impl Into]>>, -) -> Rich<'a, Theme, Renderer> +pub fn selectable_rich_text<'a, Message, Theme, Renderer>( + spans: impl Into]>>, +) -> Rich<'a, Message, Theme, Renderer> where Theme: Catalog + 'a, Renderer: text::Renderer, @@ -28,8 +30,7 @@ where } /// A bunch of [`Rich`] text. -#[derive(Debug)] -pub struct Rich<'a, Theme = iced::Theme, Renderer = iced::Renderer> +pub struct Rich<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer> where Theme: Catalog, Renderer: text::Renderer, @@ -43,9 +44,13 @@ where align_x: alignment::Horizontal, align_y: alignment::Vertical, class: Theme::Class<'a>, + + value: Value, + link_graphemes: Vec<(usize, usize)>, + on_link_pressed: Option Message + 'a>>, } -impl<'a, Theme, Renderer> Rich<'a, Theme, Renderer> +impl<'a, Message, Theme, Renderer> Rich<'a, Message, Theme, Renderer> where Theme: Catalog, Renderer: text::Renderer, @@ -62,13 +67,41 @@ where align_x: alignment::Horizontal::Left, align_y: alignment::Vertical::Top, class: Theme::default(), + + value: Value::new(""), + link_graphemes: vec![], + on_link_pressed: None, } } /// Creates a new [`Rich`] text with the given text spans. - pub fn with_spans(spans: impl Into]>>) -> Self { + pub fn with_spans(spans: impl Into]>>) -> Self { + let custom_spans = spans.into(); + + let mut i = 0; + let link_graphemes = custom_spans.iter().fold(vec![], |mut acc, span| { + let count = UnicodeSegmentation::graphemes(span.text.as_ref(), true).count(); + + if span.is_link() { + acc.push((i, i + count)); + } + + i += count; + + acc + }); + + let spans = custom_spans + .iter() + .cloned() + .map(CustomSpan::into_span) + .collect::>(); + let value = Value::new(&spans.iter().map(|s| s.text.as_ref()).join("")); + Self { spans: spans.into(), + value, + link_graphemes, ..Self::new() } } @@ -159,14 +192,15 @@ where self } - /// Adds a new text [`Span`] to the [`Rich`] text. - pub fn push(mut self, span: impl Into>) -> Self { - self.spans.to_mut().push(span.into()); - self + pub fn on_link_pressed(self, f: impl Fn(String) -> Message + 'a) -> Self { + Self { + on_link_pressed: Some(Box::new(f)), + ..self + } } } -impl<'a, Theme, Renderer> Default for Rich<'a, Theme, Renderer> +impl<'a, Message, Theme, Renderer> Default for Rich<'a, Message, Theme, Renderer> where Theme: Catalog, Renderer: text::Renderer, @@ -176,13 +210,44 @@ where } } +#[derive(Debug, Clone, PartialEq)] +pub enum CustomSpan<'a, Font = iced::Font> { + Span(Span<'a, Font>), + Link(Span<'a, Font>), +} + +impl<'a, Font> CustomSpan<'a, Font> { + fn is_link(&self) -> bool { + matches!(self, Self::Link(_)) + } + + fn into_span(self) -> Span<'a, Font> { + match self { + CustomSpan::Span(s) => s, + CustomSpan::Link(s) => s, + } + } +} + +impl<'a, Font> Deref for CustomSpan<'a, Font> { + type Target = Span<'a, Font>; + + fn deref(&self) -> &Self::Target { + match self { + CustomSpan::Span(s) => s, + CustomSpan::Link(s) => s, + } + } +} + struct State { spans: Vec>, paragraph: P, interaction: Interaction, } -impl<'a, Message, Theme, Renderer> Widget for Rich<'a, Theme, Renderer> +impl<'a, Message, Theme, Renderer> Widget + for Rich<'a, Message, Theme, Renderer> where Theme: Catalog, Renderer: text::Renderer, @@ -231,11 +296,11 @@ where &mut self, tree: &mut Tree, event: iced::Event, - _layout: Layout<'_>, + layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, _clipboard: &mut dyn iced::advanced::Clipboard, - _shell: &mut iced::advanced::Shell<'_, Message>, + shell: &mut iced::advanced::Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { let state = tree.state.downcast_mut::>(); @@ -260,6 +325,18 @@ where } else { state.interaction = Interaction::Idle; } + + if let Some(cursor) = cursor.position_in(layout.bounds()) { + if let Some(link) = + is_over_link(cursor, &state.paragraph, &self.value, &self.link_graphemes) + { + if let Some(f) = self.on_link_pressed.as_ref() { + shell.publish((f)(link)); + + return event::Status::Captured; + } + } + } } iced::Event::Mouse(mouse::Event::CursorMoved { .. }) | iced::Event::Touch(touch::Event::FingerMoved { .. }) => { @@ -357,14 +434,20 @@ where fn mouse_interaction( &self, - _state: &Tree, + tree: &Tree, layout: Layout<'_>, cursor: mouse::Cursor, _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { - if cursor.position_over(layout.bounds()).is_some() { - mouse::Interaction::Text + let state = tree.state.downcast_ref::>(); + + if let Some(cursor) = cursor.position_in(layout.bounds()) { + if is_over_link(cursor, &state.paragraph, &self.value, &self.link_graphemes).is_some() { + mouse::Interaction::Pointer + } else { + mouse::Interaction::Text + } } else { mouse::Interaction::default() } @@ -380,18 +463,37 @@ where let state = tree.state.downcast_ref::>(); let bounds = layout.bounds(); - let value = Value::new(&self.spans.iter().map(|s| s.text.as_ref()).join("")); if let Some(selection) = state .interaction .selection() - .and_then(|raw| selection(raw, bounds, &state.paragraph, &value)) + .and_then(|raw| selection(raw, bounds, &state.paragraph, &self.value)) { - let content = value.select(selection.start, selection.end).to_string(); + let content = self + .value + .select(selection.start, selection.end) + .to_string(); operation.custom(&mut (bounds.y, content), None); } } } +fn is_over_link( + cursor: Point, + paragraph: &P, + value: &Value, + link_graphemes: &[(usize, usize)], +) -> Option { + if let Some(pos) = selection::find_cursor_position(paragraph, value, cursor) { + link_graphemes.iter().find_map(|(start, end)| { + (*start..*end) + .contains(&pos) + .then(|| value.select(*start, *end).to_string()) + }) + } else { + None + } +} + fn layout( state: &mut State, renderer: &Renderer, @@ -453,7 +555,8 @@ where }) } -impl<'a, Theme, Renderer> FromIterator> for Rich<'a, Theme, Renderer> +impl<'a, Message, Theme, Renderer> FromIterator> + for Rich<'a, Message, Theme, Renderer> where Theme: Catalog, Renderer: text::Renderer, @@ -466,13 +569,14 @@ where } } -impl<'a, Message, Theme, Renderer> From> +impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Theme: Catalog + 'a, Renderer: text::Renderer + 'a, + Message: 'a, { - fn from(text: Rich<'a, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> { + fn from(text: Rich<'a, Message, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> { Element::new(text) } } diff --git a/src/widget/selectable_text/selection.rs b/src/widget/selectable_text/selection.rs index 80df1b4f3..2a1d168e7 100644 --- a/src/widget/selectable_text/selection.rs +++ b/src/widget/selectable_text/selection.rs @@ -78,7 +78,7 @@ pub fn selection( }) } -fn find_cursor_position( +pub fn find_cursor_position( paragraph: &P, value: &Value, cursor_position: Point, From 0857485c7055c06ac2626937ef2ba11799144593 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 20 Jul 2024 08:54:30 -0700 Subject: [PATCH 04/15] Only open link if pressed and released over --- src/widget/selectable_rich_text.rs | 47 ++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/widget/selectable_rich_text.rs b/src/widget/selectable_rich_text.rs index 967d90557..2cbb054b8 100644 --- a/src/widget/selectable_rich_text.rs +++ b/src/widget/selectable_rich_text.rs @@ -244,6 +244,7 @@ struct State { spans: Vec>, paragraph: P, interaction: Interaction, + pressed_link: Option<(usize, usize)>, } impl<'a, Message, Theme, Renderer> Widget @@ -261,6 +262,7 @@ where spans: Vec::new(), paragraph: Renderer::Paragraph::default(), interaction: Interaction::default(), + pressed_link: None, }) } @@ -308,6 +310,14 @@ where match event { iced::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | iced::Event::Touch(touch::Event::FingerPressed { .. }) => { + if let Some(cursor) = cursor.position_in(layout.bounds()) { + if let Some((start, end)) = + is_over_link(cursor, &state.paragraph, &self.value, &self.link_graphemes) + { + state.pressed_link = Some((start, end)); + } + } + if let Some(cursor) = cursor.position() { state.interaction = Interaction::Selecting(selection::Raw { start: cursor, @@ -320,23 +330,32 @@ where iced::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | iced::Event::Touch(touch::Event::FingerLifted { .. }) | iced::Event::Touch(touch::Event::FingerLost { .. }) => { - if let Interaction::Selecting(raw) = state.interaction { - state.interaction = Interaction::Selected(raw); - } else { - state.interaction = Interaction::Idle; - } + let pressed_link = state.pressed_link.take(); if let Some(cursor) = cursor.position_in(layout.bounds()) { - if let Some(link) = + if let Some((start, end)) = is_over_link(cursor, &state.paragraph, &self.value, &self.link_graphemes) { - if let Some(f) = self.on_link_pressed.as_ref() { - shell.publish((f)(link)); + // We clicked and released over same link + if Some((start, end)) == pressed_link { + let link = self.value.select(start, end).to_string(); - return event::Status::Captured; + if let Some(f) = self.on_link_pressed.as_ref() { + shell.publish((f)(link)); + + state.interaction = Interaction::Idle; + + return event::Status::Captured; + } } } } + + if let Interaction::Selecting(raw) = state.interaction { + state.interaction = Interaction::Selected(raw); + } else { + state.interaction = Interaction::Idle; + } } iced::Event::Mouse(mouse::Event::CursorMoved { .. }) | iced::Event::Touch(touch::Event::FingerMoved { .. }) => { @@ -482,13 +501,11 @@ fn is_over_link( paragraph: &P, value: &Value, link_graphemes: &[(usize, usize)], -) -> Option { +) -> Option<(usize, usize)> { if let Some(pos) = selection::find_cursor_position(paragraph, value, cursor) { - link_graphemes.iter().find_map(|(start, end)| { - (*start..*end) - .contains(&pos) - .then(|| value.select(*start, *end).to_string()) - }) + link_graphemes + .iter() + .find_map(|(start, end)| (*start..=*end).contains(&pos).then_some((*start, *end))) } else { None } From 56d0b9e4a55489656650f15fe07b9ba9600dffbe Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 20 Jul 2024 08:55:33 -0700 Subject: [PATCH 05/15] Rename span name --- src/widget.rs | 8 +++----- src/widget/selectable_rich_text.rs | 21 ++++++++++----------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/widget.rs b/src/widget.rs index 1cf895b88..2c31b7823 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -52,12 +52,10 @@ pub fn message_content<'a, M: 'a>( .iter() .map(|fragment| match fragment { data::message::Fragment::Text(s) => { - selectable_rich_text::CustomSpan::Span(span(s)) + selectable_rich_text::SelectableSpan::Span(span(s)) } - data::message::Fragment::Url(s) => selectable_rich_text::CustomSpan::Link( - span(s.as_str()) - // .font(font::MONO_BOLD.clone()) - .color(theme.colors().action.base), + data::message::Fragment::Url(s) => selectable_rich_text::SelectableSpan::Link( + span(s.as_str()).color(theme.colors().action.base), ), }) .collect::>(), diff --git a/src/widget/selectable_rich_text.rs b/src/widget/selectable_rich_text.rs index 2cbb054b8..a7fa3e429 100644 --- a/src/widget/selectable_rich_text.rs +++ b/src/widget/selectable_rich_text.rs @@ -20,7 +20,7 @@ use super::selectable_text::{selection, Catalog, Interaction, Style, StyleFn}; /// Creates a new [`Rich`] text widget with the provided spans. pub fn selectable_rich_text<'a, Message, Theme, Renderer>( - spans: impl Into]>>, + spans: impl Into]>>, ) -> Rich<'a, Message, Theme, Renderer> where Theme: Catalog + 'a, @@ -49,7 +49,6 @@ where link_graphemes: Vec<(usize, usize)>, on_link_pressed: Option Message + 'a>>, } - impl<'a, Message, Theme, Renderer> Rich<'a, Message, Theme, Renderer> where Theme: Catalog, @@ -75,7 +74,7 @@ where } /// Creates a new [`Rich`] text with the given text spans. - pub fn with_spans(spans: impl Into]>>) -> Self { + pub fn with_spans(spans: impl Into]>>) -> Self { let custom_spans = spans.into(); let mut i = 0; @@ -94,7 +93,7 @@ where let spans = custom_spans .iter() .cloned() - .map(CustomSpan::into_span) + .map(SelectableSpan::into_span) .collect::>(); let value = Value::new(&spans.iter().map(|s| s.text.as_ref()).join("")); @@ -211,31 +210,31 @@ where } #[derive(Debug, Clone, PartialEq)] -pub enum CustomSpan<'a, Font = iced::Font> { +pub enum SelectableSpan<'a, Font = iced::Font> { Span(Span<'a, Font>), Link(Span<'a, Font>), } -impl<'a, Font> CustomSpan<'a, Font> { +impl<'a, Font> SelectableSpan<'a, Font> { fn is_link(&self) -> bool { matches!(self, Self::Link(_)) } fn into_span(self) -> Span<'a, Font> { match self { - CustomSpan::Span(s) => s, - CustomSpan::Link(s) => s, + SelectableSpan::Span(s) => s, + SelectableSpan::Link(s) => s, } } } -impl<'a, Font> Deref for CustomSpan<'a, Font> { +impl<'a, Font> Deref for SelectableSpan<'a, Font> { type Target = Span<'a, Font>; fn deref(&self) -> &Self::Target { match self { - CustomSpan::Span(s) => s, - CustomSpan::Link(s) => s, + SelectableSpan::Span(s) => s, + SelectableSpan::Link(s) => s, } } } From 8d7aeded62d7fa0fc35d4248ddcee1dc54bc4983 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 20 Jul 2024 08:58:57 -0700 Subject: [PATCH 06/15] Fix deser error nuking history --- data/src/message.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/data/src/message.rs b/data/src/message.rs index b8d46c70f..eb947c0ff 100644 --- a/data/src/message.rs +++ b/data/src/message.rs @@ -169,7 +169,8 @@ impl<'de> Deserialize<'de> for Message { server_time: DateTime, direction: Direction, target: Target, - content: Content, + // New field + content: Option, // Old field before we had fragments text: Option, } @@ -185,8 +186,11 @@ impl<'de> Deserialize<'de> for Message { let content = if let Some(text) = text { parse_fragments(text) - } else { + } else if let Some(content) = content { content + } else { + // Unreachable + Content::Plain("".to_string()) }; Ok(Message { From 7e4ea6475453b6395563fe914417892876ac107a Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 20 Jul 2024 09:05:33 -0700 Subject: [PATCH 07/15] Lets not forget about any URLs in this stuff --- data/src/message.rs | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/data/src/message.rs b/data/src/message.rs index eb947c0ff..5af95e4f6 100644 --- a/data/src/message.rs +++ b/data/src/message.rs @@ -452,7 +452,9 @@ fn content( let topic = topic.as_ref()?; - Some(plain(format!(" ∙ {user} changed topic to {topic}"))) + Some(parse_fragments(format!( + " ∙ {user} changed topic to {topic}" + ))) } Command::PART(target, text) => { let raw_user = message.user()?; @@ -465,14 +467,16 @@ fn content( .map(|text| format!(" ({text})")) .unwrap_or_default(); - Some(plain(format!("⟵ {user} has left the channel{text}"))) + Some(parse_fragments(format!( + "⟵ {user} has left the channel{text}" + ))) } Command::JOIN(target, _) => { let raw_user = message.user()?; let user = resolve_attributes(&raw_user, target).unwrap_or(raw_user); (user.nickname() != *our_nick).then(|| { - plain(format!( + parse_fragments(format!( "⟶ {} has joined the channel", user.formatted(config.buffer.server_messages.join.username_format) )) @@ -492,7 +496,9 @@ fn content( format!("{victim} has") }; - Some(plain(format!("⟵ {target} been kicked by {user}{comment}"))) + Some(parse_fragments(format!( + "⟵ {target} been kicked by {user}{comment}" + ))) } Command::MODE(target, modes, args) if proto::is_channel(target) => { let raw_user = message.user()?; @@ -510,7 +516,9 @@ fn content( .collect::>() .join(" "); - Some(plain(format!(" ∙ {user} sets mode {modes} {args}"))) + Some(parse_fragments(format!( + " ∙ {user} sets mode {modes} {args}" + ))) } Command::PRIVMSG(_, text) => { // Check if a synthetic action message @@ -526,7 +534,7 @@ fn content( Command::Numeric(RPL_TOPIC, params) => { let topic = params.get(2)?; - Some(plain(format!(" ∙ topic is {topic}"))) + Some(parse_fragments(format!(" ∙ topic is {topic}"))) } Command::Numeric(RPL_ENDOFWHOIS, _) => { // We skip the end message of a WHOIS. @@ -547,7 +555,7 @@ fn content( let duration = std::time::Duration::from_secs(idle); let idle_readable = formatter.convert(duration); - Some(plain(format!( + Some(parse_fragments(format!( " ∙ {nick} signed on at {sign_on_datetime} and has been idle for {idle_readable}" ))) } @@ -556,7 +564,7 @@ fn content( let server = params.get(2)?; let region = params.get(3)?; - Some(plain(format!( + Some(parse_fragments(format!( " ∙ {nick} is connected on {server} ({region})" ))) } @@ -565,7 +573,7 @@ fn content( let userhost = format!("{}@{}", params.get(2)?, params.get(3)?); let real_name = params.get(5)?; - Some(plain(format!( + Some(parse_fragments(format!( " ∙ {nick} has userhost {userhost} and real name '{real_name}'" ))) } @@ -573,27 +581,29 @@ fn content( let nick = params.get(1)?; let channels = params.get(2)?; - Some(plain(format!(" ∙ {nick} is in {channels}"))) + Some(parse_fragments(format!(" ∙ {nick} is in {channels}"))) } Command::Numeric(RPL_WHOISACTUALLY, params) => { let nick = params.get(1)?; let ip = params.get(2)?; let status_text = params.get(3)?; - Some(plain(format!(" ∙ {nick} {status_text} {ip}"))) + Some(parse_fragments(format!(" ∙ {nick} {status_text} {ip}"))) } Command::Numeric(RPL_WHOISSECURE, params) => { let nick = params.get(1)?; let status_text = params.get(2)?; - Some(plain(format!(" ∙ {nick} {status_text}"))) + Some(parse_fragments(format!(" ∙ {nick} {status_text}"))) } Command::Numeric(RPL_WHOISACCOUNT, params) => { let nick = params.get(1)?; let account = params.get(2)?; let status_text = params.get(3)?; - Some(plain(format!(" ∙ {nick} {status_text} {account}"))) + Some(parse_fragments(format!( + " ∙ {nick} {status_text} {account}" + ))) } Command::Numeric(RPL_TOPICWHOTIME, params) => { let nick = params.get(2)?; @@ -606,7 +616,9 @@ fn content( .and_then(Posix::datetime)? .to_rfc2822(); - Some(plain(format!(" ∙ topic set by {nick} at {datetime}"))) + Some(parse_fragments(format!( + " ∙ topic set by {nick} at {datetime}" + ))) } Command::Numeric(RPL_CHANNELMODEIS, params) => { let mode = params @@ -616,7 +628,7 @@ fn content( .collect::>() .join(" "); - Some(plain(format!(" ∙ Channel mode is {mode}"))) + Some(parse_fragments(format!(" ∙ Channel mode is {mode}"))) } Command::Numeric(RPL_AWAY, params) => { let user = params.get(1)?; @@ -625,7 +637,7 @@ fn content( .map(|away| format!(" ({away})")) .unwrap_or_default(); - Some(plain(format!(" ∙ {user} is away{away_message}"))) + Some(parse_fragments(format!(" ∙ {user} is away{away_message}"))) } Command::Numeric(_, responses) | Command::Unknown(_, responses) => Some(parse_fragments( responses From 9c426d0c412b4ead7f62afda0ed12b4399694cb6 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 20 Jul 2024 10:24:38 -0700 Subject: [PATCH 08/15] Only allow http(s), irc(s) and www leading URLs --- data/src/message.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/message.rs b/data/src/message.rs index 5af95e4f6..9cc3461bf 100644 --- a/data/src/message.rs +++ b/data/src/message.rs @@ -13,7 +13,7 @@ use crate::{ctcp, Config, User}; static URL_REGEX: Lazy = Lazy::new(|| { Regex::new( - r#"(?i)(https?:\/\/)([\da-z\.-]+\.[a-z\.]{2,6}|[\d\.]+)([\/:?=&#]{1}[\da-z\.-]+)*[\/\?]?"#, + r#"(?i)((https?|ircs?):\/\/|www\.)[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)"#, ) .unwrap() }); From 57bf71a87d598038be7067480d647371e4f2b7c7 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 20 Jul 2024 10:33:36 -0700 Subject: [PATCH 09/15] Add url links to topic --- data/src/client.rs | 8 +++++--- src/buffer/channel.rs | 16 +++++++++------- src/buffer/channel/topic.rs | 18 ++++++++++++------ src/buffer/query.rs | 8 ++++---- src/buffer/scroll_view.rs | 2 +- src/buffer/server.rs | 4 ++-- src/buffer/user_context.rs | 17 +++++++++++------ src/screen/dashboard/pane.rs | 2 +- src/widget.rs | 6 +++--- 9 files changed, 48 insertions(+), 33 deletions(-) diff --git a/data/src/client.rs b/data/src/client.rs index 21f385103..c06ac153a 100644 --- a/data/src/client.rs +++ b/data/src/client.rs @@ -898,7 +898,9 @@ impl Client { } Command::TOPIC(channel, topic) => { if let Some(channel) = self.chanmap.get_mut(channel) { - topic.clone_into(&mut channel.topic.text); + if let Some(text) = topic { + channel.topic.content = Some(message::parse_fragments(text.clone())); + } channel.topic.who = message .user() @@ -908,7 +910,7 @@ impl Client { } Command::Numeric(RPL_TOPIC, args) => { if let Some(channel) = self.chanmap.get_mut(&args[1]) { - channel.topic.text = Some(args.get(2)?.to_owned()); + channel.topic.content = Some(message::parse_fragments(args.get(2)?.to_owned())); } // Exclude topic message from history to prevent spam during dev #[cfg(feature = "dev")] @@ -1396,7 +1398,7 @@ impl Channel { #[derive(Default, Debug, Clone)] pub struct Topic { - pub text: Option, + pub content: Option, pub who: Option, pub time: Option>, } diff --git a/src/buffer/channel.rs b/src/buffer/channel.rs index 1d1c2e109..56a32b546 100644 --- a/src/buffer/channel.rs +++ b/src/buffer/channel.rs @@ -81,7 +81,7 @@ pub fn view<'a>( let space = selectable_text(" "); let text = message_content( - message, + &message.content, theme, scroll_view::Message::Link, theme::selectable_text::default, @@ -108,7 +108,7 @@ pub fn view<'a>( } message::Source::Server(server) => { let message = message_content( - message, + &message.content, theme, scroll_view::Message::Link, move |theme| { @@ -124,7 +124,7 @@ pub fn view<'a>( } message::Source::Action => { let message = message_content( - message, + &message.content, theme, scroll_view::Message::Link, theme::selectable_text::accent, @@ -134,7 +134,7 @@ pub fn view<'a>( } message::Source::Internal(message::source::Internal::Status(status)) => { let message = message_content( - message, + &message.content, theme, scroll_view::Message::Link, move |theme| { @@ -160,7 +160,7 @@ pub fn view<'a>( // If topic toggles from None to Some then it messes with messages' scroll state, // so produce a zero-height placeholder when topic is None. - let topic = topic(state, clients, users, our_user, settings, config) + let topic = topic(state, clients, users, our_user, settings, config, theme) .unwrap_or_else(|| column![].into()); let show_text_input = match config.buffer.text_input.visibility { @@ -268,7 +268,7 @@ impl Channel { } Message::UserContext(message) => ( Task::none(), - Some(Event::UserContext(user_context::update(message))), + user_context::update(message).map(Event::UserContext), ), } } @@ -289,6 +289,7 @@ fn topic<'a>( our_user: Option<&'a User>, settings: &'a channel::Settings, config: &'a Config, + theme: &'a Theme, ) -> Option> { if !settings.topic.enabled { return None; @@ -298,7 +299,7 @@ fn topic<'a>( Some( topic::view( - topic.text.as_deref()?, + topic.content.as_ref()?, topic.who.as_deref(), topic.time.as_ref(), config.buffer.channel.topic.max_lines, @@ -306,6 +307,7 @@ fn topic<'a>( &state.buffer(), our_user, config, + theme, ) .map(Message::UserContext), ) diff --git a/src/buffer/channel/topic.rs b/src/buffer/channel/topic.rs index be900e0e2..87ef6f4c6 100644 --- a/src/buffer/channel/topic.rs +++ b/src/buffer/channel/topic.rs @@ -1,15 +1,15 @@ use chrono::{DateTime, Utc}; use data::user::Nick; -use data::{Buffer, Config, User}; +use data::{message, Buffer, Config, User}; use iced::widget::{column, container, horizontal_rule, row, scrollable, Scrollable}; use iced::Length; use super::user_context; -use crate::theme; -use crate::widget::{double_pass, selectable_text, Element}; +use crate::widget::{double_pass, message_content, selectable_text, Element}; +use crate::{theme, Theme}; pub fn view<'a>( - text: &'a str, + content: &'a message::Content, who: Option<&'a str>, time: Option<&'a DateTime>, max_lines: u16, @@ -17,6 +17,7 @@ pub fn view<'a>( buffer: &Buffer, our_user: Option<&'a User>, config: &'a Config, + theme: &'a Theme, ) -> Element<'a, user_context::Message> { let set_by = who.and_then(|who| { let nick = Nick::from(who.split('!').next()?); @@ -49,8 +50,13 @@ pub fn view<'a>( ]) }); - let content = column![selectable_text(text).style(theme::selectable_text::transparent)] - .push_maybe(set_by); + let content = column![message_content( + content, + theme, + user_context::Message::Link, + theme::selectable_text::transparent + )] + .push_maybe(set_by); let scrollable = Scrollable::new(container(content).width(Length::Fill).padding(padding())) .direction(scrollable::Direction::Vertical( diff --git a/src/buffer/query.rs b/src/buffer/query.rs index 60bb25255..1251e026c 100644 --- a/src/buffer/query.rs +++ b/src/buffer/query.rs @@ -69,7 +69,7 @@ pub fn view<'a>( let space = selectable_text(" "); let message = message_content( - message, + &message.content, theme, scroll_view::Message::Link, theme::selectable_text::default, @@ -88,7 +88,7 @@ pub fn view<'a>( } message::Source::Server(server) => { let message = message_content( - message, + &message.content, theme, scroll_view::Message::Link, move |theme| { @@ -104,7 +104,7 @@ pub fn view<'a>( } message::Source::Action => { let message = message_content( - message, + &message.content, theme, scroll_view::Message::Link, theme::selectable_text::accent, @@ -114,7 +114,7 @@ pub fn view<'a>( } message::Source::Internal(message::source::Internal::Status(status)) => { let message = message_content( - message, + &message.content, theme, scroll_view::Message::Link, move |theme| { diff --git a/src/buffer/scroll_view.rs b/src/buffer/scroll_view.rs index 45a23fcc9..5b4442731 100644 --- a/src/buffer/scroll_view.rs +++ b/src/buffer/scroll_view.rs @@ -213,7 +213,7 @@ impl State { Message::UserContext(message) => { return ( Task::none(), - Some(Event::UserContext(user_context::update(message))), + user_context::update(message).map(Event::UserContext), ); } Message::Link(link) => { diff --git a/src/buffer/server.rs b/src/buffer/server.rs index 74df21c4f..52fff9622 100644 --- a/src/buffer/server.rs +++ b/src/buffer/server.rs @@ -42,7 +42,7 @@ pub fn view<'a>( match message.target.source() { message::Source::Server(server) => { let message = message_content( - message, + &message.content, theme, scroll_view::Message::Link, move |theme| { @@ -58,7 +58,7 @@ pub fn view<'a>( } message::Source::Internal(message::source::Internal::Status(status)) => { let message = message_content( - message, + &message.content, theme, scroll_view::Message::Link, move |theme| { diff --git a/src/buffer/user_context.rs b/src/buffer/user_context.rs index 1c273e089..4b007c495 100644 --- a/src/buffer/user_context.rs +++ b/src/buffer/user_context.rs @@ -53,6 +53,7 @@ pub enum Message { SingleClick(Nick), ToggleAccessLevel(Nick, String), SendFile(Nick), + Link(String), } #[derive(Debug, Clone)] @@ -64,13 +65,17 @@ pub enum Event { SendFile(Nick), } -pub fn update(message: Message) -> Event { +pub fn update(message: Message) -> Option { match message { - Message::Whois(nick) => Event::SendWhois(nick), - Message::Query(nick) => Event::OpenQuery(nick), - Message::SingleClick(nick) => Event::SingleClick(nick), - Message::ToggleAccessLevel(nick, mode) => Event::ToggleAccessLevel(nick, mode), - Message::SendFile(nick) => Event::SendFile(nick), + Message::Whois(nick) => Some(Event::SendWhois(nick)), + Message::Query(nick) => Some(Event::OpenQuery(nick)), + Message::SingleClick(nick) => Some(Event::SingleClick(nick)), + Message::ToggleAccessLevel(nick, mode) => Some(Event::ToggleAccessLevel(nick, mode)), + Message::SendFile(nick) => Some(Event::SendFile(nick)), + Message::Link(link) => { + let _ = open::that(link); + None + } } } diff --git a/src/screen/dashboard/pane.rs b/src/screen/dashboard/pane.rs index bd0ac4af9..ac7455735 100644 --- a/src/screen/dashboard/pane.rs +++ b/src/screen/dashboard/pane.rs @@ -154,7 +154,7 @@ impl TitleBar { if let Buffer::Channel(state) = &buffer { // Show topic button only if there is a topic to show if let Some(topic) = clients.get_channel_topic(&state.server, &state.channel) { - if topic.text.is_some() { + if topic.content.is_some() { let topic_button = button(center(icon::topic())) .padding(5) .width(22) diff --git a/src/widget.rs b/src/widget.rs index 2c31b7823..07f338c0a 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -1,5 +1,5 @@ #![allow(dead_code)] -use data::Message; +use data::message; use iced::widget::span; use crate::Theme; @@ -40,12 +40,12 @@ pub type Container<'a, Message> = iced::widget::Container<'a, Message, Theme, Re pub type Button<'a, Message> = iced::widget::Button<'a, Message, Theme>; pub fn message_content<'a, M: 'a>( - message: &'a Message, + content: &'a message::Content, theme: &'a Theme, on_link_pressed: impl Fn(String) -> M + 'a, style: impl Fn(&Theme) -> selectable_text::Style + 'a, ) -> Element<'a, M> { - match &message.content { + match content { data::message::Content::Plain(text) => selectable_text(text).style(style).into(), data::message::Content::Fragments(fragments) => selectable_rich_text( fragments From ab0a0237c4a0688e6a31768ce1386bcc1db7087e Mon Sep 17 00:00:00 2001 From: Casper Rogild Storm Date: Sun, 21 Jul 2024 00:00:22 +0200 Subject: [PATCH 10/15] Added new halloy icons file --- assets/icons/fontello-config.json | 76 ++++++++++++++++++++++++++++++ fonts/halloy-icons.ttf | Bin 0 -> 7864 bytes fonts/icons.ttf | Bin 425684 -> 0 bytes src/buffer/file_transfers.rs | 8 ++-- src/font.rs | 6 ++- src/icon.rs | 32 +++++-------- src/screen/dashboard/pane.rs | 2 +- src/screen/dashboard/sidebar.rs | 8 ++-- 8 files changed, 102 insertions(+), 30 deletions(-) create mode 100755 assets/icons/fontello-config.json create mode 100755 fonts/halloy-icons.ttf delete mode 100644 fonts/icons.ttf diff --git a/assets/icons/fontello-config.json b/assets/icons/fontello-config.json new file mode 100755 index 000000000..67d9f1cd1 --- /dev/null +++ b/assets/icons/fontello-config.json @@ -0,0 +1,76 @@ +{ + "name": "halloy-icons", + "css_prefix_text": "icon-", + "css_use_suffix": false, + "hinting": true, + "units_per_em": 1000, + "ascent": 850, + "glyphs": [ + { + "uid": "6a12c2b74456ea21cc984e11dec227a1", + "css": "globe", + "code": 59392, + "src": "entypo" + }, + { + "uid": "ecf8edb95c3f45eb433b4cce7ba9f740", + "css": "users", + "code": 59396, + "src": "entypo" + }, + { + "uid": "c76b7947c957c9b78b11741173c8349b", + "css": "attention", + "code": 59405, + "src": "fontawesome" + }, + { + "uid": "130380e481a7defc690dfb24123a1f0c", + "css": "circle", + "code": 61713, + "src": "fontawesome" + }, + { + "uid": "3a3f65b92c9b2fcbb3a8dcd0d42ee806", + "css": "resize-full-1", + "code": 59393, + "src": "entypo" + }, + { + "uid": "108c8ac13badc00ce66da014436e6eb1", + "css": "resize-small-1", + "code": 59397, + "src": "entypo" + }, + { + "uid": "14017aae737730faeda4a6fd8fb3a5f0", + "css": "check", + "code": 59398, + "src": "entypo" + }, + { + "uid": "c709da589c923ba3c2ad48d9fc563e93", + "css": "cancel-1", + "code": 59407, + "src": "entypo" + }, + { + "uid": "875ab0f6d417223f6ec76ff7f0a4baed", + "css": "tag-1", + "code": 59395, + "src": "entypo" + }, + { + "uid": "b08cfe8039de2ce815686aced2caef06", + "css": "download", + "code": 59394, + "src": "entypo" + }, + { + "uid": "7222571caa5c15f83dcfd447c58d68d9", + "css": "search-1", + "code": 59400, + "src": "entypo" + } + ] +} \ No newline at end of file diff --git a/fonts/halloy-icons.ttf b/fonts/halloy-icons.ttf new file mode 100755 index 0000000000000000000000000000000000000000..f8e6b272b303d95a9bd3c594037cbd7e029a7212 GIT binary patch literal 7864 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 z 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%0DGN-Y20XwrRAo``Q( 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 diff --git a/src/buffer/file_transfers.rs b/src/buffer/file_transfers.rs index 38c2d1ac4..6d015e808 100644 --- a/src/buffer/file_transfers.rs +++ b/src/buffer/file_transfers.rs @@ -226,18 +226,18 @@ mod transfer_row { file_transfer::Status::PendingApproval => { buttons = buttons.push(row_button(icon::checkmark(), Message::Approve(transfer.id))); - buttons = buttons.push(row_button(icon::close(), Message::Clear(transfer.id))); + buttons = buttons.push(row_button(icon::cancel(), Message::Clear(transfer.id))); } file_transfer::Status::PendingReverseConfirmation | file_transfer::Status::Queued | file_transfer::Status::Ready => { - buttons = buttons.push(row_button(icon::close(), Message::Clear(transfer.id))); + buttons = buttons.push(row_button(icon::cancel(), Message::Clear(transfer.id))); } file_transfer::Status::Active { .. } | file_transfer::Status::Completed { .. } => { - buttons = buttons.push(row_button(icon::close(), Message::Clear(transfer.id))); + buttons = buttons.push(row_button(icon::cancel(), Message::Clear(transfer.id))); } file_transfer::Status::Failed { .. } => { - buttons = buttons.push(row_button(icon::close(), Message::Clear(transfer.id))); + buttons = buttons.push(row_button(icon::cancel(), Message::Clear(transfer.id))); } } diff --git a/src/font.rs b/src/font.rs index 7677768f2..2e1caf1ed 100644 --- a/src/font.rs +++ b/src/font.rs @@ -6,7 +6,7 @@ use iced::font; pub static MONO: Font = Font::new(false); pub static MONO_BOLD: Font = Font::new(true); -pub const ICON: iced::Font = iced::Font::with_name("bootstrap-icons"); +pub const ICON: iced::Font = iced::Font::with_name("halloy-icons"); #[derive(Debug, Clone)] pub struct Font { @@ -63,6 +63,8 @@ pub fn load() -> Vec> { include_bytes!("../fonts/iosevka-term-italic.ttf") .as_slice() .into(), - include_bytes!("../fonts/icons.ttf").as_slice().into(), + include_bytes!("../fonts/halloy-icons.ttf") + .as_slice() + .into(), ] } diff --git a/src/icon.rs b/src/icon.rs index 0f6d41c76..d1c39284a 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -4,54 +4,48 @@ use iced::widget::text::LineHeight; use crate::widget::Text; use crate::{font, theme}; -// Based off https://github.com/iced-rs/iced_aw/blob/main/src/graphics/icons/bootstrap.rs - pub fn dot<'a>() -> Text<'a> { - to_text('\u{f287}') + to_text('\u{F111}') } pub fn error<'a>() -> Text<'a> { - to_text('\u{f33a}') -} - -pub fn globe<'a>() -> Text<'a> { - to_text('\u{f3ef}') + to_text('\u{E80D}') } -pub fn wifi_off<'a>() -> Text<'a> { - to_text('\u{f61b}') +pub fn connected<'a>() -> Text<'a> { + to_text('\u{E800}') } -pub fn close<'a>() -> Text<'a> { - to_text('\u{f659}') +pub fn cancel<'a>() -> Text<'a> { + to_text('\u{E80F}') } pub fn maximize<'a>() -> Text<'a> { - to_text('\u{f14a}') + to_text('\u{E801}') } pub fn restore<'a>() -> Text<'a> { - to_text('\u{f149}') + to_text('\u{E805}') } pub fn people<'a>() -> Text<'a> { - to_text('\u{f4db}') + to_text('\u{E804}') } pub fn topic<'a>() -> Text<'a> { - to_text('\u{f5af}') + to_text('\u{E803}') } pub fn search<'a>() -> Text<'a> { - to_text('\u{f52a}') + to_text('\u{E808}') } pub fn checkmark<'a>() -> Text<'a> { - to_text('\u{f633}') + to_text('\u{E806}') } pub fn file_transfer<'a>() -> Text<'a> { - to_text('\u{f1c7}') + to_text('\u{E802}') } fn to_text<'a>(unicode: char) -> Text<'a> { diff --git a/src/screen/dashboard/pane.rs b/src/screen/dashboard/pane.rs index ac7455735..8cc69d03c 100644 --- a/src/screen/dashboard/pane.rs +++ b/src/screen/dashboard/pane.rs @@ -222,7 +222,7 @@ impl TitleBar { // Add delete as long as it's not a single empty buffer if !(panes == 1 && matches!(buffer, Buffer::Empty)) { - let close_button = button(center(icon::close())) + let close_button = button(center(icon::cancel())) .padding(5) .width(22) .height(22) diff --git a/src/screen/dashboard/sidebar.rs b/src/screen/dashboard/sidebar.rs index b79a1ba05..859c4fa64 100644 --- a/src/screen/dashboard/sidebar.rs +++ b/src/screen/dashboard/sidebar.rs @@ -255,11 +255,11 @@ fn buffer_button<'a>( let row = match &buffer { Buffer::Server(server) => row![ - if connected { - icon::globe() + icon::connected().style(if connected { + theme::text::primary } else { - icon::wifi_off() - }, + theme::text::error + }), text(server.to_string()) .style(theme::text::primary) .shaping(text::Shaping::Advanced) From 7271251fb0da0e662775f883c5c7d9f7de88abde Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sun, 21 Jul 2024 09:27:27 -0700 Subject: [PATCH 11/15] Use new iced span link API --- Cargo.lock | 20 +- Cargo.toml | 4 +- src/widget.rs | 14 +- src/widget/selectable_rich_text.rs | 286 ++++++++++++----------------- 4 files changed, 139 insertions(+), 185 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80a305bf6..947c6c31e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1959,7 +1959,7 @@ dependencies = [ [[package]] name = "iced" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" +source = "git+https://github.com/iced-rs/iced?rev=5443e4d8289873895587d856dbcf46f980bda6ab#5443e4d8289873895587d856dbcf46f980bda6ab" dependencies = [ "iced_core", "iced_futures", @@ -1973,7 +1973,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" +source = "git+https://github.com/iced-rs/iced?rev=5443e4d8289873895587d856dbcf46f980bda6ab#5443e4d8289873895587d856dbcf46f980bda6ab" dependencies = [ "bitflags 2.5.0", "bytes", @@ -1992,7 +1992,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" +source = "git+https://github.com/iced-rs/iced?rev=5443e4d8289873895587d856dbcf46f980bda6ab#5443e4d8289873895587d856dbcf46f980bda6ab" dependencies = [ "futures", "iced_core", @@ -2006,7 +2006,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" +source = "git+https://github.com/iced-rs/iced?rev=5443e4d8289873895587d856dbcf46f980bda6ab#5443e4d8289873895587d856dbcf46f980bda6ab" dependencies = [ "bitflags 2.5.0", "bytemuck", @@ -2027,7 +2027,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" +source = "git+https://github.com/iced-rs/iced?rev=5443e4d8289873895587d856dbcf46f980bda6ab#5443e4d8289873895587d856dbcf46f980bda6ab" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2039,7 +2039,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" +source = "git+https://github.com/iced-rs/iced?rev=5443e4d8289873895587d856dbcf46f980bda6ab#5443e4d8289873895587d856dbcf46f980bda6ab" dependencies = [ "bytes", "iced_core", @@ -2051,7 +2051,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" +source = "git+https://github.com/iced-rs/iced?rev=5443e4d8289873895587d856dbcf46f980bda6ab#5443e4d8289873895587d856dbcf46f980bda6ab" dependencies = [ "bytemuck", "cosmic-text", @@ -2066,7 +2066,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" +source = "git+https://github.com/iced-rs/iced?rev=5443e4d8289873895587d856dbcf46f980bda6ab#5443e4d8289873895587d856dbcf46f980bda6ab" dependencies = [ "bitflags 2.5.0", "bytemuck", @@ -2085,7 +2085,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" +source = "git+https://github.com/iced-rs/iced?rev=5443e4d8289873895587d856dbcf46f980bda6ab#5443e4d8289873895587d856dbcf46f980bda6ab" dependencies = [ "iced_renderer", "iced_runtime", @@ -2100,7 +2100,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.13.0-dev" -source = "git+https://github.com/iced-rs/iced?rev=c851e67734ec0c761adfd7881c576856ea38734b#c851e67734ec0c761adfd7881c576856ea38734b" +source = "git+https://github.com/iced-rs/iced?rev=5443e4d8289873895587d856dbcf46f980bda6ab#5443e4d8289873895587d856dbcf46f980bda6ab" dependencies = [ "iced_futures", "iced_graphics", diff --git a/Cargo.toml b/Cargo.toml index 76e2048bd..79995fff5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,5 +53,5 @@ embed-resource = "2.1.1" members = ["data", "ipc", "irc", "irc/proto"] [patch.crates-io] -iced = { git = "https://github.com/iced-rs/iced", rev = "c851e67734ec0c761adfd7881c576856ea38734b" } -iced_core = { git = "https://github.com/iced-rs/iced", rev = "c851e67734ec0c761adfd7881c576856ea38734b" } +iced = { git = "https://github.com/iced-rs/iced", rev = "5443e4d8289873895587d856dbcf46f980bda6ab" } +iced_core = { git = "https://github.com/iced-rs/iced", rev = "5443e4d8289873895587d856dbcf46f980bda6ab" } diff --git a/src/widget.rs b/src/widget.rs index 07f338c0a..5ec3575d7 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -42,7 +42,7 @@ pub type Button<'a, Message> = iced::widget::Button<'a, Message, Theme>; pub fn message_content<'a, M: 'a>( content: &'a message::Content, theme: &'a Theme, - on_link_pressed: impl Fn(String) -> M + 'a, + on_link: impl Fn(String) -> M + 'a, style: impl Fn(&Theme) -> selectable_text::Style + 'a, ) -> Element<'a, M> { match content { @@ -51,16 +51,14 @@ pub fn message_content<'a, M: 'a>( fragments .iter() .map(|fragment| match fragment { - data::message::Fragment::Text(s) => { - selectable_rich_text::SelectableSpan::Span(span(s)) - } - data::message::Fragment::Url(s) => selectable_rich_text::SelectableSpan::Link( - span(s.as_str()).color(theme.colors().action.base), - ), + data::message::Fragment::Text(s) => span(s), + data::message::Fragment::Url(s) => span(s.as_str()) + .color(theme.colors().action.base) + .link(s.as_str().to_string()), }) .collect::>(), ) - .on_link_pressed(on_link_pressed) + .on_link(on_link) .style(style) .into(), } diff --git a/src/widget/selectable_rich_text.rs b/src/widget/selectable_rich_text.rs index a7fa3e429..85691a5b7 100644 --- a/src/widget/selectable_rich_text.rs +++ b/src/widget/selectable_rich_text.rs @@ -1,41 +1,49 @@ -use std::borrow::Cow; -use std::ops::Deref; - -use iced::advanced::layout::{self, Layout}; -use iced::advanced::renderer::{self, Quad}; +use iced::advanced::graphics::core::touch; +use iced::advanced::layout; +use iced::advanced::renderer; +use iced::advanced::renderer::Quad; use iced::advanced::text::{self, Paragraph, Span, Text}; use iced::advanced::widget::tree::{self, Tree}; use iced::advanced::widget::Operation; -use iced::advanced::Widget; +use iced::advanced::{Clipboard, Layout, Shell, Widget}; +use iced::alignment; +use iced::event; +use iced::mouse; +use iced::widget; use iced::widget::text::{LineHeight, Shaping}; use iced::widget::text_input::Value; -use iced::{alignment, event, Border, Shadow}; -use iced::{mouse, touch}; -use iced::{widget, Point}; -use iced::{Color, Element, Length, Pixels, Rectangle, Size}; +use iced::Border; +use iced::Length; +use iced::Point; +use iced::Shadow; +use iced::{self, Color, Element, Event, Pixels, Rectangle, Size}; use itertools::Itertools; -use unicode_segmentation::UnicodeSegmentation; use super::selectable_text::{selection, Catalog, Interaction, Style, StyleFn}; +use std::borrow::Cow; + /// Creates a new [`Rich`] text widget with the provided spans. -pub fn selectable_rich_text<'a, Message, Theme, Renderer>( - spans: impl Into]>>, -) -> Rich<'a, Message, Theme, Renderer> +pub fn selectable_rich_text<'a, Message, Link, Theme, Renderer>( + spans: impl Into]>>, +) -> Rich<'a, Message, Link, Theme, Renderer> where - Theme: Catalog + 'a, + Link: Clone + 'static, + Theme: Catalog, Renderer: text::Renderer, { Rich::with_spans(spans) } /// A bunch of [`Rich`] text. -pub struct Rich<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer> +#[allow(missing_debug_implementations)] +pub struct Rich<'a, Message, Link = (), Theme = iced::Theme, Renderer = iced::Renderer> where + Link: Clone + 'static, Theme: Catalog, Renderer: text::Renderer, { - spans: Cow<'a, [Span<'a, Renderer::Font>]>, + spans: Cow<'a, [Span<'a, Link, Renderer::Font>]>, size: Option, line_height: LineHeight, width: Length, @@ -44,13 +52,12 @@ where align_x: alignment::Horizontal, align_y: alignment::Vertical, class: Theme::Class<'a>, - - value: Value, - link_graphemes: Vec<(usize, usize)>, - on_link_pressed: Option Message + 'a>>, + on_link: Option Message + 'a>>, } -impl<'a, Message, Theme, Renderer> Rich<'a, Message, Theme, Renderer> + +impl<'a, Message, Link, Theme, Renderer> Rich<'a, Message, Link, Theme, Renderer> where + Link: Clone + 'static, Theme: Catalog, Renderer: text::Renderer, { @@ -66,41 +73,14 @@ where align_x: alignment::Horizontal::Left, align_y: alignment::Vertical::Top, class: Theme::default(), - - value: Value::new(""), - link_graphemes: vec![], - on_link_pressed: None, + on_link: None, } } /// Creates a new [`Rich`] text with the given text spans. - pub fn with_spans(spans: impl Into]>>) -> Self { - let custom_spans = spans.into(); - - let mut i = 0; - let link_graphemes = custom_spans.iter().fold(vec![], |mut acc, span| { - let count = UnicodeSegmentation::graphemes(span.text.as_ref(), true).count(); - - if span.is_link() { - acc.push((i, i + count)); - } - - i += count; - - acc - }); - - let spans = custom_spans - .iter() - .cloned() - .map(SelectableSpan::into_span) - .collect::>(); - let value = Value::new(&spans.iter().map(|s| s.text.as_ref()).join("")); - + pub fn with_spans(spans: impl Into]>>) -> Self { Self { spans: spans.into(), - value, - link_graphemes, ..Self::new() } } @@ -184,23 +164,22 @@ where }) } - /// Sets the default style class of the [`Rich`] text. - #[must_use] - pub fn class(mut self, class: impl Into>) -> Self { - self.class = class.into(); + /// Sets the message handler for link clicks on the [`Rich`] text. + pub fn on_link(mut self, on_link: impl Fn(Link) -> Message + 'a) -> Self { + self.on_link = Some(Box::new(on_link)); self } - pub fn on_link_pressed(self, f: impl Fn(String) -> Message + 'a) -> Self { - Self { - on_link_pressed: Some(Box::new(f)), - ..self - } + /// Adds a new text [`Span`] to the [`Rich`] text. + pub fn push(mut self, span: impl Into>) -> Self { + self.spans.to_mut().push(span.into()); + self } } -impl<'a, Message, Theme, Renderer> Default for Rich<'a, Message, Theme, Renderer> +impl<'a, Message, Link, Theme, Renderer> Default for Rich<'a, Message, Link, Theme, Renderer> where + Link: Clone + 'static, Theme: Catalog, Renderer: text::Renderer, { @@ -209,59 +188,30 @@ where } } -#[derive(Debug, Clone, PartialEq)] -pub enum SelectableSpan<'a, Font = iced::Font> { - Span(Span<'a, Font>), - Link(Span<'a, Font>), -} - -impl<'a, Font> SelectableSpan<'a, Font> { - fn is_link(&self) -> bool { - matches!(self, Self::Link(_)) - } - - fn into_span(self) -> Span<'a, Font> { - match self { - SelectableSpan::Span(s) => s, - SelectableSpan::Link(s) => s, - } - } -} - -impl<'a, Font> Deref for SelectableSpan<'a, Font> { - type Target = Span<'a, Font>; - - fn deref(&self) -> &Self::Target { - match self { - SelectableSpan::Span(s) => s, - SelectableSpan::Link(s) => s, - } - } -} - -struct State { - spans: Vec>, +struct State { + spans: Vec>, + span_pressed: Option, paragraph: P, interaction: Interaction, - pressed_link: Option<(usize, usize)>, } -impl<'a, Message, Theme, Renderer> Widget - for Rich<'a, Message, Theme, Renderer> +impl<'a, Message, Link, Theme, Renderer> Widget + for Rich<'a, Message, Link, Theme, Renderer> where + Link: Clone + 'static, Theme: Catalog, Renderer: text::Renderer, { fn tag(&self) -> tree::Tag { - tree::Tag::of::>() + tree::Tag::of::>() } fn state(&self) -> tree::State { - tree::State::new(State { + tree::State::new(State:: { spans: Vec::new(), + span_pressed: None, paragraph: Renderer::Paragraph::default(), interaction: Interaction::default(), - pressed_link: None, }) } @@ -279,7 +229,8 @@ where limits: &layout::Limits, ) -> layout::Node { layout( - tree.state.downcast_mut::>(), + tree.state + .downcast_mut::>(), renderer, limits, self.width, @@ -296,24 +247,28 @@ where fn on_event( &mut self, tree: &mut Tree, - event: iced::Event, + event: Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, - _clipboard: &mut dyn iced::advanced::Clipboard, - shell: &mut iced::advanced::Shell<'_, Message>, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { - let state = tree.state.downcast_mut::>(); + let state = tree + .state + .downcast_mut::>(); + + let mut status = event::Status::Ignored; match event { iced::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | iced::Event::Touch(touch::Event::FingerPressed { .. }) => { - if let Some(cursor) = cursor.position_in(layout.bounds()) { - if let Some((start, end)) = - is_over_link(cursor, &state.paragraph, &self.value, &self.link_graphemes) - { - state.pressed_link = Some((start, end)); + if let Some(position) = cursor.position_in(layout.bounds()) { + if let Some(span) = state.paragraph.hit_span(position) { + state.span_pressed = Some(span); + + status = event::Status::Captured; } } @@ -329,22 +284,20 @@ where iced::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | iced::Event::Touch(touch::Event::FingerLifted { .. }) | iced::Event::Touch(touch::Event::FingerLost { .. }) => { - let pressed_link = state.pressed_link.take(); - - if let Some(cursor) = cursor.position_in(layout.bounds()) { - if let Some((start, end)) = - is_over_link(cursor, &state.paragraph, &self.value, &self.link_graphemes) - { - // We clicked and released over same link - if Some((start, end)) == pressed_link { - let link = self.value.select(start, end).to_string(); - - if let Some(f) = self.on_link_pressed.as_ref() { - shell.publish((f)(link)); - - state.interaction = Interaction::Idle; - - return event::Status::Captured; + if let Some(on_link_click) = self.on_link.as_ref() { + if let Some(span_pressed) = state.span_pressed { + state.span_pressed = None; + + if let Some(position) = cursor.position_in(layout.bounds()) { + match state.paragraph.hit_span(position) { + Some(span) if span == span_pressed => { + if let Some(link) = + self.spans.get(span).and_then(|span| span.link.clone()) + { + shell.publish(on_link_click(link)); + } + } + _ => {} } } } @@ -367,7 +320,7 @@ where _ => {} } - event::Status::Ignored + status } fn draw( @@ -386,7 +339,10 @@ where return; } - let state = tree.state.downcast_ref::>(); + let state = tree + .state + .downcast_ref::>(); + let style = theme.style(&self.class); if let Some(selection) = state @@ -458,17 +414,27 @@ where _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { - let state = tree.state.downcast_ref::>(); + if self.on_link.is_none() { + return mouse::Interaction::None; + } - if let Some(cursor) = cursor.position_in(layout.bounds()) { - if is_over_link(cursor, &state.paragraph, &self.value, &self.link_graphemes).is_some() { - mouse::Interaction::Pointer - } else { - mouse::Interaction::Text + if let Some(position) = cursor.position_in(layout.bounds()) { + let state = tree + .state + .downcast_ref::>(); + + if let Some(span) = state + .paragraph + .hit_span(position) + .and_then(|span| self.spans.get(span)) + { + if span.link.is_some() { + return mouse::Interaction::Pointer; + } } - } else { - mouse::Interaction::default() } + + mouse::Interaction::None } fn operate( @@ -478,45 +444,30 @@ where _renderer: &Renderer, operation: &mut dyn Operation<()>, ) { - let state = tree.state.downcast_ref::>(); + let state = tree + .state + .downcast_ref::>(); let bounds = layout.bounds(); + let value = Value::new(&self.spans.iter().map(|s| s.text.as_ref()).join("")); if let Some(selection) = state .interaction .selection() - .and_then(|raw| selection(raw, bounds, &state.paragraph, &self.value)) + .and_then(|raw| selection(raw, bounds, &state.paragraph, &value)) { - let content = self - .value - .select(selection.start, selection.end) - .to_string(); + let content = value.select(selection.start, selection.end).to_string(); operation.custom(&mut (bounds.y, content), None); } } } -fn is_over_link( - cursor: Point, - paragraph: &P, - value: &Value, - link_graphemes: &[(usize, usize)], -) -> Option<(usize, usize)> { - if let Some(pos) = selection::find_cursor_position(paragraph, value, cursor) { - link_graphemes - .iter() - .find_map(|(start, end)| (*start..=*end).contains(&pos).then_some((*start, *end))) - } else { - None - } -} - -fn layout( - state: &mut State, +fn layout( + state: &mut State, renderer: &Renderer, limits: &layout::Limits, width: Length, height: Length, - spans: &[Span<'_, Renderer::Font>], + spans: &[Span<'_, Link, Renderer::Font>], line_height: LineHeight, size: Option, font: Option, @@ -524,6 +475,7 @@ fn layout( vertical_alignment: alignment::Vertical, ) -> layout::Node where + Link: Clone, Renderer: text::Renderer, { layout::sized(limits, width, height, |limits| { @@ -571,13 +523,14 @@ where }) } -impl<'a, Message, Theme, Renderer> FromIterator> - for Rich<'a, Message, Theme, Renderer> +impl<'a, Message, Link, Theme, Renderer> FromIterator> + for Rich<'a, Message, Link, Theme, Renderer> where + Link: Clone + 'static, Theme: Catalog, Renderer: text::Renderer, { - fn from_iter>>(spans: T) -> Self { + fn from_iter>>(spans: T) -> Self { Self { spans: spans.into_iter().collect(), ..Self::new() @@ -585,14 +538,17 @@ where } } -impl<'a, Message, Theme, Renderer> From> +impl<'a, Message, Link, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where + Message: 'a, + Link: Clone + 'static, Theme: Catalog + 'a, Renderer: text::Renderer + 'a, - Message: 'a, { - fn from(text: Rich<'a, Message, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> { + fn from( + text: Rich<'a, Message, Link, Theme, Renderer>, + ) -> Element<'a, Message, Theme, Renderer> { Element::new(text) } } From 2cc896e6a656deba26062167a8ac6c9ca0abf749 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sun, 21 Jul 2024 09:45:55 -0700 Subject: [PATCH 12/15] Use open detached to ensure no blocking --- src/buffer/scroll_view.rs | 2 +- src/buffer/user_context.rs | 2 +- src/screen/dashboard.rs | 6 +++--- src/screen/help.rs | 4 ++-- src/screen/migration.rs | 4 ++-- src/screen/welcome.rs | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/buffer/scroll_view.rs b/src/buffer/scroll_view.rs index 5b4442731..9f7d77463 100644 --- a/src/buffer/scroll_view.rs +++ b/src/buffer/scroll_view.rs @@ -217,7 +217,7 @@ impl State { ); } Message::Link(link) => { - let _ = open::that(link); + let _ = open::that_detached(link); } } diff --git a/src/buffer/user_context.rs b/src/buffer/user_context.rs index 4b007c495..b0afe59f0 100644 --- a/src/buffer/user_context.rs +++ b/src/buffer/user_context.rs @@ -73,7 +73,7 @@ pub fn update(message: Message) -> Option { Message::ToggleAccessLevel(nick, mode) => Some(Event::ToggleAccessLevel(nick, mode)), Message::SendFile(nick) => Some(Event::SendFile(nick)), Message::Link(link) => { - let _ = open::that(link); + let _ = open::that_detached(link); None } } diff --git a/src/screen/dashboard.rs b/src/screen/dashboard.rs index 71dc8a988..95add108c 100644 --- a/src/screen/dashboard.rs +++ b/src/screen/dashboard.rs @@ -355,7 +355,7 @@ impl Dashboard { let (command, event) = match command { command_bar::Command::Version(command) => match command { command_bar::Version::Application(_) => { - let _ = open::that(RELEASE_WEBSITE); + let _ = open::that_detached(RELEASE_WEBSITE); (Task::none(), None) } }, @@ -397,11 +397,11 @@ impl Dashboard { }, command_bar::Command::Configuration(command) => match command { command_bar::Configuration::OpenDirectory => { - let _ = open::that(Config::config_dir()); + let _ = open::that_detached(Config::config_dir()); (Task::none(), None) } command_bar::Configuration::OpenWebsite => { - let _ = open::that(environment::WIKI_WEBSITE); + let _ = open::that_detached(environment::WIKI_WEBSITE); (Task::none(), None) } command_bar::Configuration::Reload => { diff --git a/src/screen/help.rs b/src/screen/help.rs index ec842baa2..e4f53dc0d 100644 --- a/src/screen/help.rs +++ b/src/screen/help.rs @@ -32,12 +32,12 @@ impl Help { match message { Message::RefreshConfiguration => Some(Event::RefreshConfiguration), Message::OpenConfigurationDirectory => { - let _ = open::that(Config::config_dir()); + let _ = open::that_detached(Config::config_dir()); None } Message::OpenWikiWebsite => { - let _ = open::that(WIKI_WEBSITE); + let _ = open::that_detached(WIKI_WEBSITE); None } diff --git a/src/screen/migration.rs b/src/screen/migration.rs index a0af4a710..17add0fe3 100644 --- a/src/screen/migration.rs +++ b/src/screen/migration.rs @@ -33,12 +33,12 @@ impl Migration { match message { Message::RefreshConfiguration => Some(Event::RefreshConfiguration), Message::OpenConfigurationDirectory => { - let _ = open::that(Config::config_dir()); + let _ = open::that_detached(Config::config_dir()); None } Message::OpenMigrationWebsite => { - let _ = open::that(MIGRATION_WEBSITE); + let _ = open::that_detached(MIGRATION_WEBSITE); None } diff --git a/src/screen/welcome.rs b/src/screen/welcome.rs index 4488f3ff5..afd62e631 100644 --- a/src/screen/welcome.rs +++ b/src/screen/welcome.rs @@ -33,12 +33,12 @@ impl Welcome { match message { Message::RefreshConfiguration => Some(Event::RefreshConfiguration), Message::OpenConfigurationDirectory => { - let _ = open::that(Config::config_dir()); + let _ = open::that_detached(Config::config_dir()); None } Message::OpenWikiWebsite => { - let _ = open::that(WIKI_WEBSITE); + let _ = open::that_detached(WIKI_WEBSITE); None } From e0c91d22f26ea1a5cebbb2c8a725265a2859739c Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sun, 21 Jul 2024 10:28:21 -0700 Subject: [PATCH 13/15] Serialize text field for downgrade compatability --- data/src/message.rs | 52 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/data/src/message.rs b/data/src/message.rs index 9cc3461bf..a22736927 100644 --- a/data/src/message.rs +++ b/data/src/message.rs @@ -1,6 +1,9 @@ +use std::borrow::Cow; + use chrono::{DateTime, Utc}; use irc::proto; use irc::proto::Command; +use itertools::Itertools; use once_cell::sync::Lazy; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -86,7 +89,7 @@ pub enum Direction { Received, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone)] pub struct Message { pub received_at: Posix, pub server_time: DateTime, @@ -158,6 +161,35 @@ impl Message { } } +impl Serialize for Message { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + #[derive(Serialize)] + struct Data<'a> { + received_at: &'a Posix, + server_time: &'a DateTime, + direction: &'a Direction, + target: &'a Target, + content: &'a Content, + // Old field before we had fragments, + // added for downgrade compatability + text: Cow<'a, str>, + } + + Data { + received_at: &self.received_at, + server_time: &self.server_time, + direction: &self.direction, + target: &self.target, + content: &self.content, + text: self.content.text(), + } + .serialize(serializer) + } +} + impl<'de> Deserialize<'de> for Message { fn deserialize(deserializer: D) -> Result where @@ -169,7 +201,7 @@ impl<'de> Deserialize<'de> for Message { server_time: DateTime, direction: Direction, target: Target, - // New field + // New field, optional for upgrade compatability content: Option, // Old field before we had fragments text: Option, @@ -184,10 +216,11 @@ impl<'de> Deserialize<'de> for Message { text, } = Data::deserialize(deserializer)?; - let content = if let Some(text) = text { - parse_fragments(text) - } else if let Some(content) = content { + let content = if let Some(content) = content { content + } else if let Some(text) = text { + // First time upgrading, convert text into content + parse_fragments(text) } else { // Unreachable Content::Plain("".to_string()) @@ -239,6 +272,15 @@ pub enum Content { Fragments(Vec), } +impl Content { + fn text(&self) -> Cow { + match self { + Content::Plain(s) => s.into(), + Content::Fragments(fragments) => fragments.iter().map(Fragment::as_str).join("").into(), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Fragment { Text(String), From 3bb813972bd7d289e6ce47be475d7ed8bfbeb7cf Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sun, 21 Jul 2024 11:26:42 -0700 Subject: [PATCH 14/15] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5a03f524..2ed775121 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Added: +- Rich formatted and clickable URLs - Support for CTCP queries CLIENTINFO, PING, SOURCE, and VERSION - Custom notification sounds. Use your own sounds or select from a few new built-in options. For more details, see [notification configuration](https://halloy.squidowl.org/configuration/notifications.html). From c74a7dae27457c823766e5aa94133923dff7548e Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sun, 21 Jul 2024 11:40:25 -0700 Subject: [PATCH 15/15] Fix www url parsing --- data/src/message.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/data/src/message.rs b/data/src/message.rs index a22736927..bdc6f1780 100644 --- a/data/src/message.rs +++ b/data/src/message.rs @@ -245,9 +245,13 @@ pub fn parse_fragments(text: String) -> Content { let mut fragments = vec![]; for (re_match, url) in URL_REGEX.find_iter(&text).filter_map(|re_match| { - Url::parse(re_match.as_str()) - .ok() - .map(|url| (re_match, url)) + let url = if re_match.as_str().starts_with("www") { + format!("https://{}", re_match.as_str()) + } else { + re_match.as_str().to_string() + }; + + Url::parse(&url).ok().map(|url| (re_match, url)) }) { if i < re_match.start() { fragments.push(Fragment::Text(text[i..re_match.start()].to_string()));