diff --git a/src/session/content/background.rs b/src/session/content/background.rs index 94604c755..b7fff55f8 100644 --- a/src/session/content/background.rs +++ b/src/session/content/background.rs @@ -1,5 +1,6 @@ use adw::prelude::*; use adw::subclass::prelude::*; +use glib::clone; use gtk::{gdk, gio, glib, graphene, gsk}; use std::cell::{Cell, RefCell}; @@ -49,7 +50,9 @@ mod imp { #[derive(Default)] pub(crate) struct Background { - pub(super) gradient_texture: RefCell>, + pub(super) background_texture: RefCell>, + pub(super) message_texture: RefCell>, + pub(super) last_size: Cell<(f32, f32)>, pub(super) shader: RefCell>, @@ -61,7 +64,8 @@ mod imp { pub(super) dark: Cell, - pub(super) colors: RefCell>, + pub(super) bg_colors: RefCell>, + pub(super) message_colors: RefCell>, } #[glib::object_subclass] @@ -102,7 +106,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); @@ -171,18 +175,20 @@ mod imp { size_changed: bool, ) { if self.progress.get() == 0.0 { - let texture = match self.gradient_texture.take() { - Some(texture) if !size_changed => texture, + let texture = match self.background_texture.take() { + Some(texture) if !size_changed => texture.clone(), _ => { - let renderer = self.obj().native().unwrap().renderer(); - renderer.render_texture(self.gradient_shader_node(bounds), Some(bounds)) + self.render_textures(bounds); + self.background_texture.take().unwrap() } }; 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)); + self.render_textures(bounds); + let texture = self.background_texture.borrow().as_ref().unwrap().clone(); + snapshot.append_texture(&texture, bounds); } } @@ -220,7 +226,24 @@ mod imp { } } - fn gradient_shader_node(&self, bounds: &graphene::Rect) -> gsk::GLShaderNode { + fn render_textures(&self, bounds: &graphene::Rect) { + let colors = [self.bg_colors.borrow(), self.message_colors.borrow()]; + + let renderer = self.obj().native().unwrap().renderer(); + + let mut textures = colors.into_iter().map(|color| { + renderer.render_texture(self.gradient_shader_node(bounds, &color), Some(bounds)) + }); + + self.background_texture.replace(textures.next()); + self.message_texture.replace(textures.next()); + } + + fn gradient_shader_node( + &self, + bounds: &graphene::Rect, + colors: &[graphene::Vec3], + ) -> gsk::GLShaderNode { let Some(gradient_shader) = &*self.shader.borrow() else { unreachable!() }; @@ -230,10 +253,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); @@ -299,7 +320,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(_) => { @@ -307,30 +328,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; + imp.bg_colors.replace(fill_colors(bg_fill)); + imp.message_colors + .replace(fill_colors(theme.outgoing_message_fill)); - 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.gradient_texture.take(); + imp.background_texture.take(); self.queue_draw(); } @@ -343,6 +345,29 @@ impl Background { } } + pub 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 fn bg_texture(&self) -> gdk::Texture { + self.imp() + .background_texture + .borrow() + .as_ref() + .unwrap() + .clone() + } + + pub fn message_texture(&self) -> gdk::Texture { + self.imp() + .message_texture + .borrow() + .as_ref() + .unwrap() + .clone() + } + fn ensure_shader(&self) { let imp = self.imp(); if imp.shader.borrow().is_none() { @@ -370,8 +395,31 @@ 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 = (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() + } + _ => 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::*; @@ -381,7 +429,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(), @@ -390,13 +438,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![0xfc27a6, 0xff9201, 0x7827ff, 0x554efe], + ) } else { - theme(dark, vec![0x94dae9, 0x9aeddb, 0x94c3f6, 0xac96f7]) + theme( + dark, + vec![0x94dae9, 0x9aeddb, 0x94c3f6, 0xac96f7], + vec![0xddffdf, 0xfff0dd, 0xffddfc, 0xddecff], + ) } } diff --git a/src/session/content/message_row/bubble.rs b/src/session/content/message_row/bubble.rs index 9ebd9e205..b1338163b 100644 --- a/src/session/content/message_row/bubble.rs +++ b/src/session/content/message_row/bubble.rs @@ -1,10 +1,11 @@ use adw::prelude::*; use glib::clone; use gtk::subclass::prelude::*; -use gtk::{gdk, glib, graphene, gsk, CompositeTemplate}; +use gtk::{glib, CompositeTemplate}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; +use crate::session::content::background::Background; use crate::session::content::message_row::{MessageIndicators, MessageLabel, MessageReply}; use crate::tdlib::{Chat, ChatType, Message, MessageSender, SponsoredMessage}; @@ -64,6 +65,7 @@ mod imp { 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] @@ -136,27 +138,28 @@ mod imp { 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 widget.has_css_class("outgoing") { - let width = widget.width() as f32; - let height = widget.height() as f32; - - let bounds = graphene::Rect::new(0.0, 0.0, width, height); - let gradient_bounds = widget.gradient_bounds(); - let [first, second] = widget.linear_gradient_colors(); - - snapshot.append_linear_gradient( - &bounds, - &graphene::Point::new(0.0, gradient_bounds.y()), - &graphene::Point::new(0.0, gradient_bounds.height()), - &[ - gsk::ColorStop::new(0.0, first), - gsk::ColorStop::new(1.0, second), - ], - ); + + if let Some(background) = self.parent_background.borrow().upgrade() { + if !background.has_css_class("fallback") { + let gradient_bounds = background.compute_bounds(self.obj().as_ref()).unwrap(); + + if widget.has_css_class("outgoing") { + snapshot.append_texture(&background.message_texture(), &gradient_bounds); + } else { + snapshot.push_opacity(0.1); + snapshot.append_texture(&background.bg_texture(), &gradient_bounds); + snapshot.pop(); + }; + } } self.parent_snapshot(snapshot); @@ -353,42 +356,10 @@ impl MessageBubble { } fn parent_list_view(&self) -> Option { - let mut parent = self.parent()?; - loop { - match parent.downcast() { - Ok(list_view) => return Some(list_view), - Err(not_list_view) => parent = not_list_view.parent()?, - } - } + self.ancestor(gtk::ListView::static_type())?.downcast().ok() } - fn gradient_bounds(&self) -> graphene::Rect { - if let Some(view) = self.imp().parent_list_view.borrow().upgrade() { - let view_bounds = view.compute_bounds(self.imp().obj().as_ref()).unwrap(); - - graphene::Rect::new( - view_bounds.x(), - view_bounds.y(), - view_bounds.width() + view_bounds.x(), - view_bounds.height() + view_bounds.y(), - ) - } else { - panic!("can't get parent ListView"); - } - } - - fn linear_gradient_colors(&self) -> [gdk::RGBA; 2] { - // default colors from iOS - if !adw::StyleManager::default().is_dark() { - [ - gdk::RGBA::new(0.91764706, 0.9882353, 0.8235294, 1.0), - gdk::RGBA::new(0.91764706, 0.9882353, 0.8235294, 1.0), - ] - } else { - [ - gdk::RGBA::new(0.21960784, 0.32156864, 0.89411765, 1.0), - gdk::RGBA::new(0.63529414, 0.3372549, 0.58431375, 1.0), - ] - } + fn parent_background(&self) -> Option { + self.ancestor(Background::static_type())?.downcast().ok() } }