diff --git a/data/resources/style.css b/data/resources/style.css index 1c9574c9d..404ad5a22 100644 --- a/data/resources/style.css +++ b/data/resources/style.css @@ -314,6 +314,10 @@ messagereply label.message { color: @window_fg_color; } +messagebubble.outgoing messagereply label.message { + color: currentColor; +} + messagesticker { border-spacing: 6px; } diff --git a/src/session/content/background.rs b/src/session/content/background.rs index 713ca213a..aee64972d 100644 --- a/src/session/content/background.rs +++ b/src/session/content/background.rs @@ -21,15 +21,20 @@ uniform vec3 color1; uniform vec3 color2; uniform vec3 color3; uniform vec3 color4; -uniform vec2 p1; -uniform vec2 p2; -uniform vec2 p3; -uniform vec2 p4; +uniform vec4 p12; +uniform vec4 p34; +uniform vec4 gradient_bounds; void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv) { + vec2 p1 = p12.xy; + vec2 p2 = p12.zw; + vec2 p3 = p34.xy; + vec2 p4 = p34.zw; + + uv = (fragCoord - gradient_bounds.xy) / gradient_bounds.zw; uv.y = 1.0 - uv.y; float dp1 = distance(uv, p1); @@ -55,7 +60,8 @@ mod imp { #[derive(Default)] pub(crate) struct Background { - pub(super) gradient_texture: RefCell>, + pub(super) background_texture: RefCell>, + pub(super) last_size: Cell<(f32, f32)>, pub(super) shader: RefCell>, @@ -67,7 +73,8 @@ mod imp { pub(super) dark: Cell, - pub(super) colors: RefCell>, + pub(super) bg_colors: RefCell>, + pub(super) message_colors: RefCell>, } #[glib::object_subclass] @@ -108,7 +115,7 @@ mod imp { let target = adw::CallbackAnimationTarget::new(clone!(@weak obj => move |progress| { let imp = obj.imp(); - imp.gradient_texture.take(); + imp.background_texture.take(); let progress = progress as f32; if progress >= 1.0 { imp.progress.set(0.0); @@ -177,18 +184,19 @@ mod imp { size_changed: bool, ) { if self.progress.get() == 0.0 { - let texture = match self.gradient_texture.take() { + let texture = match self.background_texture.take() { Some(texture) if !size_changed => texture, _ => { let renderer = self.obj().native().unwrap().renderer(); - renderer.render_texture(self.gradient_shader_node(bounds), Some(bounds)) + + renderer.render_texture(self.obj().bg_node(bounds, bounds), Some(bounds)) } }; snapshot.append_texture(&texture, bounds); - self.gradient_texture.replace(Some(texture)); + self.background_texture.replace(Some(texture)); } else { - snapshot.append_node(self.gradient_shader_node(bounds)); + snapshot.append_node(&self.obj().bg_node(bounds, bounds)); } } @@ -226,7 +234,12 @@ mod imp { } } - fn gradient_shader_node(&self, bounds: &graphene::Rect) -> gsk::GLShaderNode { + pub(super) fn gradient_shader_node( + &self, + bounds: &graphene::Rect, + gradient_bounds: &graphene::Rect, + colors: &[graphene::Vec3], + ) -> gsk::GLShaderNode { let Some(gradient_shader) = &*self.shader.borrow() else { unreachable!() }; @@ -236,10 +249,8 @@ mod imp { let progress = self.progress.get(); let phase = self.phase.get() as usize; - let colors = self.colors.borrow(); - - let &[c1, c2, c3, c4] = &colors[..] else { - unimplemented!("Unexpected color count"); + let &[c1, c2, c3, c4] = colors else { + unimplemented!("Unexpected color count") }; args_builder.set_vec3(0, &c1); @@ -247,16 +258,25 @@ mod imp { args_builder.set_vec3(2, &c3); args_builder.set_vec3(3, &c4); - let [p1, p2, p3, p4] = Self::calculate_positions(progress, phase); - args_builder.set_vec2(4, &p1); - args_builder.set_vec2(5, &p2); - args_builder.set_vec2(6, &p3); - args_builder.set_vec2(7, &p4); + let [p12, p34] = Self::calculate_positions(progress, phase); + args_builder.set_vec4(4, &p12); + args_builder.set_vec4(5, &p34); + + let gradient_bounds = { + graphene::Vec4::new( + gradient_bounds.x(), + gradient_bounds.y(), + gradient_bounds.width(), + gradient_bounds.height(), + ) + }; + + args_builder.set_vec4(6, &gradient_bounds); gsk::GLShaderNode::new(gradient_shader, bounds, &args_builder.to_args(), &[]) } - fn calculate_positions(progress: f32, phase: usize) -> [graphene::Vec2; 4] { + fn calculate_positions(progress: f32, phase: usize) -> [graphene::Vec4; 2] { static POSITIONS: [(f32, f32); 8] = [ (0.80, 0.10), (0.60, 0.20), @@ -268,7 +288,7 @@ mod imp { (0.75, 0.40), ]; - let mut points = [graphene::Vec2::new(0.0, 0.0); 4]; + let mut points = [(0.0, 0.0); 4]; for i in 0..4 { let start = POSITIONS[(i * 2 + phase) % 8]; @@ -281,10 +301,18 @@ mod imp { let x = interpolate(start.0, end.0, progress); let y = interpolate(start.1, end.1, progress); - points[i] = graphene::Vec2::new(x, y); + points[i] = (x, y); } - points + let points: Vec<_> = points + .chunks(2) + .map(|p| { + let [(x1, y1), (x2, y2)]: [(f32, f32); 2] = p.try_into().unwrap(); + graphene::Vec4::from_float([x1, y1, x2, y2]) + }) + .collect(); + + points.try_into().unwrap() } } } @@ -305,7 +333,7 @@ impl Background { imp.dark.set(background.is_dark); - let fill = match background.r#type { + let bg_fill = match background.r#type { tdlib::enums::BackgroundType::Pattern(pattern) => pattern.fill, tdlib::enums::BackgroundType::Fill(fill) => fill.fill, tdlib::enums::BackgroundType::Wallpaper(_) => { @@ -313,30 +341,11 @@ impl Background { } }; - match fill { - tdlib::enums::BackgroundFill::FreeformGradient(gradient) => { - if gradient.colors.len() != 4 { - unimplemented!("Unsupported gradient colors count"); - } - - let colors = gradient - .colors - .into_iter() - .map(|int_color| { - let r = (int_color >> 16) & 0xFF; - let g = (int_color >> 8) & 0xFF; - let b = int_color & 0xFF; - - graphene::Vec3::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0) - }) - .collect(); - - imp.colors.replace(colors); - } - _ => unimplemented!("Background fill"), - } + imp.bg_colors.replace(fill_colors(bg_fill)); + imp.message_colors + .replace(fill_colors(theme.outgoing_message_fill)); - imp.gradient_texture.take(); + imp.background_texture.take(); self.queue_draw(); } @@ -349,6 +358,32 @@ impl Background { } } + pub(crate) fn subscribe_to_redraw(&self, child: >k::Widget) { + let animation = self.imp().animation.get().unwrap(); + animation.connect_value_notify(clone!(@weak child => move |_| child.queue_draw())); + } + + pub(crate) fn bg_node( + &self, + bounds: &graphene::Rect, + gradient_bounds: &graphene::Rect, + ) -> gsk::GLShaderNode { + self.imp() + .gradient_shader_node(bounds, gradient_bounds, &self.imp().bg_colors.borrow()) + } + + pub(crate) fn message_bg_node( + &self, + bounds: &graphene::Rect, + gradient_bounds: &graphene::Rect, + ) -> gsk::GLShaderNode { + self.imp().gradient_shader_node( + bounds, + gradient_bounds, + &self.imp().message_colors.borrow(), + ) + } + fn ensure_shader(&self) { let imp = self.imp(); if imp.shader.borrow().is_none() { @@ -376,8 +411,28 @@ impl Default for Background { } } +fn fill_colors(fill: tdlib::enums::BackgroundFill) -> Vec { + match fill { + tdlib::enums::BackgroundFill::FreeformGradient(gradient) if gradient.colors.len() == 4 => { + gradient + .colors + .into_iter() + .map(|int_color| { + let [_, r, g, b] = int_color.to_be_bytes(); + graphene::Vec3::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0) + }) + .collect() + } + _ => unimplemented!("Unsupported background fill: {fill:?}"), + } +} + fn hard_coded_themes(dark: bool) -> tdlib::types::ThemeSettings { - fn theme(dark: bool, colors: Vec) -> tdlib::types::ThemeSettings { + fn theme( + dark: bool, + bg_colors: Vec, + message_colors: Vec, + ) -> tdlib::types::ThemeSettings { use tdlib::enums::BackgroundFill::*; use tdlib::enums::BackgroundType::Fill; use tdlib::types::*; @@ -387,7 +442,7 @@ fn hard_coded_themes(dark: bool) -> tdlib::types::ThemeSettings { is_default: true, is_dark: dark, r#type: Fill(BackgroundTypeFill { - fill: FreeformGradient(BackgroundFillFreeformGradient { colors }), + fill: FreeformGradient(BackgroundFillFreeformGradient { colors: bg_colors }), }), id: 0, name: String::new(), @@ -396,13 +451,25 @@ fn hard_coded_themes(dark: bool) -> tdlib::types::ThemeSettings { accent_color: 0, animate_outgoing_message_fill: false, outgoing_message_accent_color: 0, - outgoing_message_fill: Solid(BackgroundFillSolid { color: 0 }), + outgoing_message_fill: FreeformGradient(BackgroundFillFreeformGradient { + colors: message_colors, + }), } } + // tr tl bl br + if dark { - theme(dark, vec![0xd6932e, 0xbc40db, 0x4280d7, 0x614ed5]) + theme( + dark, + vec![0xd6932e, 0xbc40db, 0x4280d7, 0x614ed5], + vec![0x2d52ab, 0x4036a1, 0x9f388d, 0x9d3941], + ) } else { - theme(dark, vec![0x94dae9, 0x9aeddb, 0x94c3f6, 0xac96f7]) + theme( + dark, + vec![0x94dae9, 0x9aeddb, 0x94c3f6, 0xac96f7], + vec![0xddecff, 0xe0ddfd, 0xdbffff, 0xddffdf], + ) } } diff --git a/src/session/content/message_row/bubble.rs b/src/session/content/message_row/bubble.rs index 056a30503..b3b4ed550 100644 --- a/src/session/content/message_row/bubble.rs +++ b/src/session/content/message_row/bubble.rs @@ -4,11 +4,13 @@ use std::hash::Hash; use std::hash::Hasher; use adw::prelude::*; +use glib::clone; use gtk::glib; use gtk::subclass::prelude::*; use gtk::CompositeTemplate; use once_cell::sync::Lazy; +use crate::session::content::background::Background; use crate::session::content::message_row::MessageIndicators; use crate::session::content::message_row::MessageLabel; use crate::session::content::message_row::MessageReply; @@ -37,6 +39,8 @@ mod imp { using Adw 1; template $MessageBubble { + overflow: hidden; + Overlay overlay { Box { orientation: vertical; @@ -69,6 +73,8 @@ mod imp { pub(crate) struct MessageBubble { pub(super) sender_color_class: RefCell>, pub(super) sender_binding: RefCell>, + pub(super) parent_list_view: RefCell>, + pub(super) parent_background: RefCell>, #[template_child] pub(super) overlay: TemplateChild, #[template_child] @@ -128,6 +134,54 @@ mod imp { } impl WidgetImpl for MessageBubble { + fn realize(&self) { + self.parent_realize(); + + let widget = self.obj(); + + if let Some(view) = widget.parent_list_view() { + self.parent_list_view.replace(view.downgrade()); + view.vadjustment() + .unwrap() + .connect_value_notify(clone!(@weak widget => move |_| { + widget.queue_draw(); + })); + } + + if let Some(background) = widget.parent_background() { + self.parent_background.replace(background.downgrade()); + background.subscribe_to_redraw(widget.upcast_ref()); + } + } + + fn snapshot(&self, snapshot: >k::Snapshot) { + let widget = self.obj(); + + if let Some(background) = self.parent_background.borrow().upgrade() { + if !background.has_css_class("fallback") { + let bounds = { + let width = widget.width() as f32; + let height = widget.height() as f32; + + gtk::graphene::Rect::new(0.0, 0.0, width, height) + }; + + let gradient_bounds = background.compute_bounds(self.obj().as_ref()).unwrap(); + + if widget.has_css_class("outgoing") { + snapshot + .append_node(&background.message_bg_node(&bounds, &gradient_bounds)); + } else { + snapshot.push_opacity(0.1); + snapshot.append_node(&background.bg_node(&bounds, &gradient_bounds)); + snapshot.pop(); + }; + } + } + + self.parent_snapshot(snapshot); + } + fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) { // Limit the widget width if orientation == gtk::Orientation::Horizontal { @@ -317,4 +371,12 @@ impl MessageBubble { .set_indicators(Some(imp.indicators.clone())); } } + + fn parent_list_view(&self) -> Option { + self.ancestor(gtk::ListView::static_type())?.downcast().ok() + } + + fn parent_background(&self) -> Option { + self.ancestor(Background::static_type())?.downcast().ok() + } }