diff --git a/Cargo.lock b/Cargo.lock index 7eb08776f..6d11f2ba0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1958,7 +1958,7 @@ dependencies = [ [[package]] name = "iced" version = "0.13.0-dev" -source = "git+https://github.com/tarkah/iced?rev=1feb29ab4badb35a57ff5c612f25009e8e175766#1feb29ab4badb35a57ff5c612f25009e8e175766" +source = "git+https://github.com/iced-rs/iced?rev=6734d183594ebf89b8e6c030ea69d53ecb6b72db#6734d183594ebf89b8e6c030ea69d53ecb6b72db" dependencies = [ "iced_core", "iced_futures", @@ -1972,7 +1972,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.13.0-dev" -source = "git+https://github.com/tarkah/iced?rev=1feb29ab4badb35a57ff5c612f25009e8e175766#1feb29ab4badb35a57ff5c612f25009e8e175766" +source = "git+https://github.com/iced-rs/iced?rev=6734d183594ebf89b8e6c030ea69d53ecb6b72db#6734d183594ebf89b8e6c030ea69d53ecb6b72db" dependencies = [ "bitflags 2.5.0", "bytes", @@ -1991,7 +1991,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.13.0-dev" -source = "git+https://github.com/tarkah/iced?rev=1feb29ab4badb35a57ff5c612f25009e8e175766#1feb29ab4badb35a57ff5c612f25009e8e175766" +source = "git+https://github.com/iced-rs/iced?rev=6734d183594ebf89b8e6c030ea69d53ecb6b72db#6734d183594ebf89b8e6c030ea69d53ecb6b72db" dependencies = [ "futures", "iced_core", @@ -2005,7 +2005,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.13.0-dev" -source = "git+https://github.com/tarkah/iced?rev=1feb29ab4badb35a57ff5c612f25009e8e175766#1feb29ab4badb35a57ff5c612f25009e8e175766" +source = "git+https://github.com/iced-rs/iced?rev=6734d183594ebf89b8e6c030ea69d53ecb6b72db#6734d183594ebf89b8e6c030ea69d53ecb6b72db" dependencies = [ "bitflags 2.5.0", "bytemuck", @@ -2026,7 +2026,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.13.0-dev" -source = "git+https://github.com/tarkah/iced?rev=1feb29ab4badb35a57ff5c612f25009e8e175766#1feb29ab4badb35a57ff5c612f25009e8e175766" +source = "git+https://github.com/iced-rs/iced?rev=6734d183594ebf89b8e6c030ea69d53ecb6b72db#6734d183594ebf89b8e6c030ea69d53ecb6b72db" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2038,7 +2038,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.13.0-dev" -source = "git+https://github.com/tarkah/iced?rev=1feb29ab4badb35a57ff5c612f25009e8e175766#1feb29ab4badb35a57ff5c612f25009e8e175766" +source = "git+https://github.com/iced-rs/iced?rev=6734d183594ebf89b8e6c030ea69d53ecb6b72db#6734d183594ebf89b8e6c030ea69d53ecb6b72db" dependencies = [ "bytes", "iced_core", @@ -2050,7 +2050,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.13.0-dev" -source = "git+https://github.com/tarkah/iced?rev=1feb29ab4badb35a57ff5c612f25009e8e175766#1feb29ab4badb35a57ff5c612f25009e8e175766" +source = "git+https://github.com/iced-rs/iced?rev=6734d183594ebf89b8e6c030ea69d53ecb6b72db#6734d183594ebf89b8e6c030ea69d53ecb6b72db" dependencies = [ "bytemuck", "cosmic-text", @@ -2065,7 +2065,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.13.0-dev" -source = "git+https://github.com/tarkah/iced?rev=1feb29ab4badb35a57ff5c612f25009e8e175766#1feb29ab4badb35a57ff5c612f25009e8e175766" +source = "git+https://github.com/iced-rs/iced?rev=6734d183594ebf89b8e6c030ea69d53ecb6b72db#6734d183594ebf89b8e6c030ea69d53ecb6b72db" dependencies = [ "bitflags 2.5.0", "bytemuck", @@ -2084,7 +2084,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.13.0-dev" -source = "git+https://github.com/tarkah/iced?rev=1feb29ab4badb35a57ff5c612f25009e8e175766#1feb29ab4badb35a57ff5c612f25009e8e175766" +source = "git+https://github.com/iced-rs/iced?rev=6734d183594ebf89b8e6c030ea69d53ecb6b72db#6734d183594ebf89b8e6c030ea69d53ecb6b72db" dependencies = [ "iced_renderer", "iced_runtime", @@ -2099,7 +2099,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.13.0-dev" -source = "git+https://github.com/tarkah/iced?rev=1feb29ab4badb35a57ff5c612f25009e8e175766#1feb29ab4badb35a57ff5c612f25009e8e175766" +source = "git+https://github.com/iced-rs/iced?rev=6734d183594ebf89b8e6c030ea69d53ecb6b72db#6734d183594ebf89b8e6c030ea69d53ecb6b72db" dependencies = [ "iced_futures", "iced_graphics", diff --git a/Cargo.toml b/Cargo.toml index dbb59fe75..210346bce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,5 +58,5 @@ windows_exe_info = "0.4" members = ["data", "ipc", "irc", "irc/proto"] [patch.crates-io] -iced = { git = "https://github.com/tarkah/iced", rev = "1feb29ab4badb35a57ff5c612f25009e8e175766" } -iced_core = { git = "https://github.com/tarkah/iced", rev = "1feb29ab4badb35a57ff5c612f25009e8e175766" } +iced = { git = "https://github.com/iced-rs/iced", rev = "6734d183594ebf89b8e6c030ea69d53ecb6b72db" } +iced_core = { git = "https://github.com/iced-rs/iced", rev = "6734d183594ebf89b8e6c030ea69d53ecb6b72db" } diff --git a/book/src/guides/text-formatting.md b/book/src/guides/text-formatting.md index 21587d3e7..6601d302e 100644 --- a/book/src/guides/text-formatting.md +++ b/book/src/guides/text-formatting.md @@ -11,6 +11,8 @@ Below is a table with the supported text attributes. | _Italics_ | `_italic text_` | `$iitalic text$i` | | **Bold** | `__bold text__` | `$bbold text$b` | | **_Italic and Bold_** | `___italic and bold___` | `$b$iitalic and bold$i$b` | +| ~~Strikethrough~~ | `~~strikethrough~~` | `$sstrikethrough$s` | +| Underline | - | `$uunderline$u` | | Code | `` `code` `` | `$mcode$m` | | Spoiler | `\|\|spoiler\|\|` | - | @@ -20,8 +22,9 @@ Example /format __this is bold__ $iand this is italic$i ``` -Will render the following: -> __this is bold__ _and this is italic_ +Will render the following: + +> **this is bold** _and this is italic_ ## Color @@ -50,7 +53,7 @@ Colors - 12 - lightblue - 13 - pink - 14 - grey - - 15 - lightgrey + - 15 - lightgrey Example @@ -59,13 +62,12 @@ Example /format $c04,09foobar$c ``` -Will both render the following: +Will both render the following: foobar - ## Configuration By default, Halloy will only format text when using the `/format` command. This, however, can be changed with the `auto_format` configuration option: diff --git a/data/src/message/formatting/encode.rs b/data/src/message/formatting/encode.rs index 32f0e245c..5449df8a1 100644 --- a/data/src/message/formatting/encode.rs +++ b/data/src/message/formatting/encode.rs @@ -213,6 +213,7 @@ fn markdown<'a>( let italic = alt((relaxed_run('*', 1), strict_run('_', 1))); let bold = alt((relaxed_run('*', 2), strict_run('_', 2))); let italic_bold = alt((relaxed_run('*', 3), strict_run('_', 3))); + let strikethrough = relaxed_run('~', 2); let spoiler = relaxed_run('|', 2); let code = map( alt(( @@ -232,6 +233,7 @@ fn markdown<'a>( map(italic_bold, Markdown::ItalicBold), map(bold, Markdown::Bold), map(italic, Markdown::Italic), + map(strikethrough, Markdown::Strikethrough), map(spoiler, Markdown::Spoiler), map(code, Markdown::Code), )) @@ -282,6 +284,8 @@ fn dollar(input: &str) -> IResult<&str, Dollar> { map(tag("$b"), |_| Dollar::Bold), map(tag("$i"), |_| Dollar::Italics), map(tag("$m"), |_| Dollar::Monospace), + map(tag("$s"), |_| Dollar::Strikethrough), + map(tag("$u"), |_| Dollar::Underline), map(tag("$r"), |_| Dollar::Reset), map(start_color, |(fg, bg)| Dollar::StartColor(fg, bg)), // No valid colors after code == end @@ -346,6 +350,14 @@ impl Token { } out.push(c); } + Markdown::Strikethrough(tokens) => { + let m = Modifier::Strikethrough.char(); + out.push(m); + for token in tokens { + token.encode(out); + } + out.push(m); + } }, Token::Dollar(dollar) => match dollar { Dollar::Bold => { @@ -357,6 +369,12 @@ impl Token { Dollar::Monospace => { out.push(Modifier::Monospace.char()); } + Dollar::Strikethrough => { + out.push(Modifier::Strikethrough.char()); + } + Dollar::Underline => { + out.push(Modifier::Underline.char()); + } Dollar::Reset => { out.push(Modifier::Reset.char()); } @@ -383,6 +401,7 @@ enum Markdown { Bold(Vec), Italic(Vec), ItalicBold(Vec), + Strikethrough(Vec), Code(Vec), Spoiler(Vec), } @@ -392,6 +411,8 @@ enum Dollar { Bold, Italics, Monospace, + Strikethrough, + Underline, Reset, StartColor(Color, Option), EndColor, diff --git a/src/buffer/input_view/format_tooltip.txt b/src/buffer/input_view/format_tooltip.txt index f778d9256..6218b4493 100644 --- a/src/buffer/input_view/format_tooltip.txt +++ b/src/buffer/input_view/format_tooltip.txt @@ -4,11 +4,14 @@ __bold__ ___italic & bold___ `code` ||spoiler|| +~~strikethrough~~ Toggles: $b - bold $i - italic $m - monospace +$s - strikethrough +$u - underline $r - reset Color: diff --git a/src/widget.rs b/src/widget.rs index 11b4ffa69..138e9a9e4 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -1,6 +1,5 @@ #![allow(dead_code)] use data::message; -use iced::advanced::text::Background; use iced::border; use iced::widget::span; @@ -62,23 +61,22 @@ pub fn message_content<'a, M: 'a>( .color_maybe( formatting .fg - .and_then(|color| color.into_iced(theme.colors())) - .or_else(|| { - formatting.monospace.then_some(theme.colors().error.darker) - }), + .and_then(|color| color.into_iced(theme.colors())), ) .background_maybe( formatting .bg - .and_then(|color| color.into_iced(theme.colors())) - .map(Background::from) - .or_else(|| { - formatting.monospace.then_some(Background { - color: theme.colors().background.lighter, - border: border::rounded(3), - }) - }), - ); + .and_then(|color| color.into_iced(theme.colors())), + ) + .underline(formatting.underline) + .strikethrough(formatting.strikethrough); + + if formatting.monospace { + span = span + .color(theme.colors().error.darker) + .background(theme.colors().background.lighter) + .border(border::rounded(3)); + } match (formatting.bold, formatting.italics) { (true, true) => { diff --git a/src/widget/selectable_rich_text.rs b/src/widget/selectable_rich_text.rs index 9248e7990..44c4d4875 100644 --- a/src/widget/selectable_rich_text.rs +++ b/src/widget/selectable_rich_text.rs @@ -2,7 +2,7 @@ use iced::advanced::graphics::core::touch; use iced::advanced::layout; use iced::advanced::renderer; use iced::advanced::renderer::Quad; -use iced::advanced::text::Background; +use iced::advanced::text::Highlight; use iced::advanced::text::{self, Paragraph, Span, Text}; use iced::advanced::widget::tree::{self, Tree}; use iced::advanced::widget::Operation; @@ -17,7 +17,8 @@ use iced::Border; use iced::Length; use iced::Point; use iced::Shadow; -use iced::{self, Color, Element, Event, Pixels, Rectangle, Size}; +use iced::Vector; +use iced::{self, Background, Color, Element, Event, Pixels, Rectangle, Size}; use itertools::Itertools; use super::selectable_text::{selection, Catalog, Interaction, Style, StyleFn}; @@ -194,7 +195,7 @@ struct State { span_pressed: Option, paragraph: P, interaction: Interaction, - shown_spoiler: Option<(usize, Color, Background)>, + shown_spoiler: Option<(usize, Color, Highlight)>, } impl<'a, Message, Link, Theme, Renderer> Widget @@ -341,8 +342,8 @@ where if state.shown_spoiler.is_none() { // Find if spoiler is hovered for (index, span) in state.spans.iter().enumerate() { - if let Some((fg, bg)) = span.color.zip(span.background) { - let is_spoiler = fg == bg.color; + if let Some((fg, highlight)) = span.color.zip(span.highlight) { + let is_spoiler = highlight.background == Background::Color(fg); if is_spoiler && state @@ -351,7 +352,7 @@ where .into_iter() .any(|bounds| bounds.contains(cursor)) { - state.shown_spoiler = Some((index, fg, bg)); + state.shown_spoiler = Some((index, fg, highlight)); break; } } @@ -362,7 +363,7 @@ where // Safe we just got this index let span = &mut state.spans[index]; span.color = None; - span.background = None; + span.highlight = None; state.paragraph = Renderer::Paragraph::with_spans(text_with_spans( state.spans.as_ref(), )); @@ -370,10 +371,10 @@ where } } // Hide spoiler - else if let Some((index, fg, bg)) = state.shown_spoiler.take() { + else if let Some((index, fg, highlight)) = state.shown_spoiler.take() { if let Some(span) = state.spans.get_mut(index) { span.color = Some(fg); - span.background = Some(bg); + span.highlight = Some(highlight); } state.paragraph = Renderer::Paragraph::with_spans(text_with_spans(state.spans.as_ref())); @@ -392,7 +393,7 @@ where theme: &Theme, defaults: &renderer::Style, layout: Layout<'_>, - _cursor_position: mouse::Cursor, + cursor: mouse::Cursor, viewport: &Rectangle, ) { let bounds = layout.bounds(); @@ -407,20 +408,88 @@ where let style = theme.style(&self.class); - // Draw backgrounds + let hovered_span = cursor + .position_in(layout.bounds()) + .and_then(|position| state.paragraph.hit_span(position)); + for (index, span) in state.spans.iter().enumerate() { - if let Some(background) = span.background { + let is_hovered_link = span.link.is_some() && Some(index) == hovered_span; + + if span.highlight.is_some() || span.underline || span.strikethrough || is_hovered_link { let translation = layout.position() - Point::ORIGIN; + let regions = state.paragraph.span_bounds(index); + + if let Some(highlight) = span.highlight { + for bounds in ®ions { + let bounds = Rectangle::new( + bounds.position() - Vector::new(span.padding.left, span.padding.top), + bounds.size() + + Size::new(span.padding.horizontal(), span.padding.vertical()), + ); + + renderer.fill_quad( + renderer::Quad { + bounds: bounds + translation, + border: highlight.border, + ..Default::default() + }, + highlight.background, + ); + } + } - for bounds in state.paragraph.span_bounds(index) { - renderer.fill_quad( - renderer::Quad { - bounds: bounds + translation, - border: background.border, - ..Default::default() - }, - background.color, - ); + if span.underline || span.strikethrough || is_hovered_link { + let size = span.size.or(self.size).unwrap_or(renderer.default_size()); + + let line_height = span + .line_height + .unwrap_or(self.line_height) + .to_absolute(size); + + let color = span.color.or(style.color).unwrap_or(defaults.text_color); + + let baseline = + translation + Vector::new(0.0, size.0 + (line_height.0 - size.0) / 2.0); + + let snapped = |rect: Rectangle| { + let fract = viewport.y.fract(); + let offset = if fract >= 0.5 { -(1.0 - fract) } else { fract }; + let adj = (rect.y - offset).round() + offset; + + Rectangle { y: adj, ..rect } + }; + + if span.underline || is_hovered_link { + for bounds in ®ions { + renderer.fill_quad( + renderer::Quad { + bounds: snapped(Rectangle::new( + bounds.position() + baseline + - Vector::new(0.0, size.0 * 0.08), + Size::new(bounds.width, 1.0), + )), + ..Default::default() + }, + color, + ); + } + } + + if span.strikethrough { + for bounds in ®ions { + renderer.fill_quad( + renderer::Quad { + bounds: snapped(Rectangle::new( + bounds.position() + baseline + - Vector::new(0.0, size.0 / 2.0), + Size::new(bounds.width, 1.0), + )), + ..Default::default() + }, + color, + ); + } + } } } } @@ -582,7 +651,7 @@ where if let Some((index, _, _)) = state.shown_spoiler { if let Some(span) = state.spans.get_mut(index) { span.color = None; - span.background = None; + span.highlight = None; } } @@ -610,7 +679,7 @@ where if let Some((index, _, _)) = state.shown_spoiler { if let Some(span) = state.spans.get_mut(index) { span.color = None; - span.background = None; + span.highlight = None; } }