diff --git a/data/resources/style-dark.css b/data/resources/style-dark.css index cfe905a26..470a9c5f7 100644 --- a/data/resources/style-dark.css +++ b/data/resources/style-dark.css @@ -2,9 +2,8 @@ background-color: @dark_2; } -/* sticker must be repainted to a text color in messages */ -messagesticker.needs-repainting > overlay > widget > widget { - filter: invert(1); +vectorpath { + color: alpha(#404040, 0.8); } .chat-list row .unread-count-muted { diff --git a/data/resources/style.css b/data/resources/style.css index 76ad5de2f..02edc31f9 100644 --- a/data/resources/style.css +++ b/data/resources/style.css @@ -318,6 +318,10 @@ messagesticker { border-spacing: 6px; } +vectorpath { + color: alpha(black, 0.2); +} + .event-row { font-size: smaller; font-weight: bold; diff --git a/src/components/mod.rs b/src/components/mod.rs index f5efdd186..17fc12143 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -2,8 +2,10 @@ mod avatar; mod message_entry; mod snow; mod sticker; +mod vector_path; pub(crate) use self::avatar::Avatar; pub(crate) use self::message_entry::MessageEntry; pub(crate) use self::snow::Snow; pub(crate) use self::sticker::Sticker; +use self::vector_path::VectorPath; diff --git a/src/components/sticker.rs b/src/components/sticker.rs index a181c450d..58f760961 100644 --- a/src/components/sticker.rs +++ b/src/components/sticker.rs @@ -6,8 +6,9 @@ use gtk::{gio, glib}; use tdlib::enums::StickerFormat; use tdlib::types::Sticker as TdSticker; +use super::VectorPath; use crate::session::Session; -use crate::utils::{decode_image_from_path, spawn}; +use crate::utils::{color_matrix_from_color, decode_image_from_path, spawn}; mod imp { use super::*; @@ -18,6 +19,8 @@ mod imp { pub(crate) struct Sticker { pub(super) file_id: Cell, pub(super) aspect_ratio: Cell, + pub(super) recolor: Cell, + pub(super) is_loaded: Cell, pub(super) child: RefCell>, #[property(get, set = Self::set_longer_side_size)] @@ -52,11 +55,15 @@ mod imp { } impl WidgetImpl for Sticker { - fn measure(&self, orientation: gtk::Orientation, _for_size: i32) -> (i32, i32, i32, i32) { + fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) { let size = self.longer_side_size.get(); let aspect_ratio = self.aspect_ratio.get(); - let min_size = 1; + let min_size = if let Some(child) = &*self.child.borrow_mut() { + child.measure(orientation, for_size).0 + } else { + 1 + }; let size = if let gtk::Orientation::Horizontal = orientation { if aspect_ratio >= 1.0 { @@ -79,6 +86,17 @@ mod imp { child.allocate(width, height, baseline, None); } } + + fn snapshot(&self, snapshot: >k::Snapshot) { + if self.recolor.get() && self.is_loaded.get() { + let (color_matrix, color_offset) = color_matrix_from_color(self.obj().color()); + snapshot.push_color_matrix(&color_matrix, &color_offset); + self.parent_snapshot(snapshot); + snapshot.pop(); + } else { + self.parent_snapshot(snapshot); + } + } } impl Sticker { @@ -103,12 +121,15 @@ impl Sticker { return; } - // TODO: draw sticker outline with cairo - self.set_child(None); + self.set_child(VectorPath::new(&sticker.outline).upcast(), false); let aspect_ratio = sticker.width as f64 / sticker.height as f64; imp.aspect_ratio.set(aspect_ratio); + let recolor = matches!(&sticker.full_type, + tdlib::enums::StickerFullType::CustomEmoji(data) if data.needs_repainting); + imp.recolor.set(recolor); + let format = sticker.format; spawn(clone!(@weak self as obj, @weak session => async move { @@ -181,18 +202,17 @@ impl Sticker { // Skip if widget was recycled by ListView if self.imp().file_id.get() == file_id { - self.set_child(Some(widget)); + self.set_child(widget, true); } } - fn set_child(&self, child: Option) { + fn set_child(&self, child: gtk::Widget, is_loaded: bool) { let imp = self.imp(); + imp.is_loaded.set(is_loaded); - if let Some(ref child) = child { - child.set_parent(self); - } + child.set_parent(self); - if let Some(old) = imp.child.replace(child) { + if let Some(old) = imp.child.replace(Some(child)) { old.unparent() } } diff --git a/src/components/vector_path.rs b/src/components/vector_path.rs new file mode 100644 index 000000000..9de0f3d9c --- /dev/null +++ b/src/components/vector_path.rs @@ -0,0 +1,95 @@ +use gtk::prelude::*; +use gtk::subclass::prelude::*; +use gtk::{glib, graphene, gsk}; +use tdlib::types::ClosedVectorPath; + +use crate::utils::color_matrix_from_color; + +mod imp { + use super::*; + use std::cell::RefCell; + + #[derive(Default)] + pub(crate) struct VectorPath { + pub(super) node: RefCell>, + } + + #[glib::object_subclass] + impl ObjectSubclass for VectorPath { + const NAME: &'static str = "ComponentsVectorPath"; + type Type = super::VectorPath; + type ParentType = gtk::Widget; + + fn class_init(klass: &mut Self::Class) { + klass.set_css_name("vectorpath"); + } + } + + impl ObjectImpl for VectorPath {} + + impl WidgetImpl for VectorPath { + fn snapshot(&self, snapshot: >k::Snapshot) { + let widget = self.obj(); + + let factor = (widget.width() as f32).max(widget.height() as f32) / 512.0; + snapshot.scale(factor, factor); + + let (color_matrix, color_offset) = color_matrix_from_color(widget.color()); + snapshot.push_color_matrix(&color_matrix, &color_offset); + if let Some(node) = &*self.node.borrow() { + snapshot.append_node(node); + } + snapshot.pop(); + } + } +} + +glib::wrapper! { + pub(crate) struct VectorPath(ObjectSubclass) + @extends gtk::Widget; +} + +impl VectorPath { + pub fn new(outline: &[ClosedVectorPath]) -> Self { + let obj: Self = glib::Object::new(); + obj.imp().node.replace(path_node(outline)); + obj + } +} + +fn path_node(outline: &[ClosedVectorPath]) -> Option { + use tdlib::enums::VectorPathCommand::{CubicBezierCurve, Line}; + use tdlib::types::VectorPathCommandCubicBezierCurve as Curve; + + let snapshot = gtk::Snapshot::new(); + let context = snapshot.append_cairo(&graphene::Rect::new(0.0, 0.0, 512.0, 512.0)); + + for closed_path in outline { + let e = match closed_path.commands.iter().last().unwrap() { + Line(line) => &line.end_point, + CubicBezierCurve(curve) => &curve.end_point, + }; + context.move_to(e.x, e.y); + + for command in &closed_path.commands { + match command { + Line(line) => { + let e = &line.end_point; + context.line_to(e.x, e.y); + } + CubicBezierCurve(curve) => { + let Curve { + start_control_point: sc, + end_control_point: ec, + end_point: e, + } = curve; + + context.curve_to(sc.x, sc.y, ec.x, ec.y, e.x, e.y); + } + } + } + } + _ = context.fill(); + + snapshot.to_node() +} diff --git a/src/session/content/message_row/sticker.rs b/src/session/content/message_row/sticker.rs index bac6ff27d..2989d562c 100644 --- a/src/session/content/message_row/sticker.rs +++ b/src/session/content/message_row/sticker.rs @@ -162,14 +162,6 @@ impl MessageBaseExt for MessageSticker { _ => unreachable!(), }; - // TODO: that should be handled a bit better in the future - match &sticker.full_type { - StickerFullType::CustomEmoji(data) if data.needs_repainting => { - self.add_css_class("needs-repainting") - } - _ => self.remove_css_class("needs-repainting"), - } - let (size, margin_bottom) = if is_emoji { (EMOJI_SIZE, 8) } else { diff --git a/src/utils.rs b/src/utils.rs index 70ab1a4da..04c9b2140 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,5 @@ use gettextrs::gettext; -use gtk::{gdk, glib}; +use gtk::{gdk, glib, graphene}; use image::io::Reader as ImageReader; use locale_config::Locale; use once_cell::sync::Lazy; @@ -278,3 +278,12 @@ pub(crate) fn decode_image_from_path(path: &str) -> Result (graphene::Matrix, graphene::Vec4) { + let color_offset = graphene::Vec4::new(color.red(), color.green(), color.blue(), 0.0); + let mut matrix = [0.0; 16]; + matrix[15] = color.alpha(); + let color_matrix = graphene::Matrix::from_float(matrix); + + (color_matrix, color_offset) +}