Skip to content

Commit

Permalink
feat(chat-history): Support chat themes
Browse files Browse the repository at this point in the history
  • Loading branch information
yuraiz committed Jun 19, 2023
1 parent 05b00cf commit e780d7b
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 31 deletions.
120 changes: 95 additions & 25 deletions src/session/content/background.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ mod imp {

#[derive(Default)]
pub(crate) struct Background {
pub(super) chat_theme: RefCell<Option<tdlib::types::ChatTheme>>,

pub(super) background_texture: RefCell<Option<gdk::Texture>>,

pub(super) last_size: Cell<(f32, f32)>,
Expand Down Expand Up @@ -95,10 +97,10 @@ mod imp {
self.pattern.set(pattern).unwrap();

let style_manager = adw::StyleManager::default();
obj.set_theme(hard_coded_themes(style_manager.is_dark()));
obj.refresh_theme(style_manager.is_dark());

style_manager.connect_dark_notify(clone!(@weak obj => move |style_manager| {
obj.set_theme(hard_coded_themes(style_manager.is_dark()))
obj.refresh_theme(style_manager.is_dark());
}));

if style_manager.is_high_contrast() {
Expand Down Expand Up @@ -234,6 +236,40 @@ mod imp {
}
}

pub(super) fn fill_node(
&self,
bounds: &graphene::Rect,
gradient_bounds: &graphene::Rect,
colors: &[graphene::Vec3],
) -> gsk::RenderNode {
match colors.len() {
1 => gsk::ColorNode::new(&vec3_to_rgba(&colors[0]), bounds).upcast(),
2 => gsk::LinearGradientNode::new(
bounds,
&gradient_bounds.top_left(),
&gradient_bounds.bottom_left(),
&[
gsk::ColorStop::new(0.0, vec3_to_rgba(&colors[0])),
gsk::ColorStop::new(1.0, vec3_to_rgba(&colors[1])),
],
)
.upcast(),
3 => {
log::error!("Three color gradients aren't supported yet");

let mut colors = colors.to_vec();
colors.push(colors[2]);

self.gradient_shader_node(bounds, gradient_bounds, &colors)
.upcast()
}
4 => self
.gradient_shader_node(bounds, gradient_bounds, colors)
.upcast(),
_ => unreachable!("Unsupported color count"),
}
}

pub(super) fn gradient_shader_node(
&self,
bounds: &graphene::Rect,
Expand Down Expand Up @@ -327,29 +363,41 @@ impl Background {
glib::Object::new()
}

pub(crate) fn set_theme(&self, theme: tdlib::types::ThemeSettings) {
let Some(background) = theme.background else { return; };
pub(crate) fn set_chat_theme(&self, theme: Option<tdlib::types::ChatTheme>) {
self.imp().chat_theme.replace(theme);
self.refresh_theme(adw::StyleManager::default().is_dark());
}

pub(crate) fn set_theme(&self, theme: &tdlib::types::ThemeSettings) {
let Some(background) = &theme.background else { return; };
let imp = self.imp();

imp.dark.set(background.is_dark);

let bg_fill = match background.r#type {
tdlib::enums::BackgroundType::Pattern(pattern) => pattern.fill,
tdlib::enums::BackgroundType::Fill(fill) => fill.fill,
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(_) => {
unimplemented!("Wallpaper chat background")
}
};

imp.bg_colors.replace(fill_colors(bg_fill));
imp.message_colors
.replace(fill_colors(theme.outgoing_message_fill));
.replace(fill_colors(&theme.outgoing_message_fill));

imp.background_texture.take();
self.queue_draw();
}

pub(crate) fn animate(&self) {
let nothing_to_animate = self.imp().bg_colors.borrow().len() <= 2
&& self.imp().message_colors.borrow().len() <= 2;

if nothing_to_animate {
return;
}

let animation = self.imp().animation.get().unwrap();

let val = animation.value();
Expand All @@ -367,21 +415,18 @@ impl Background {
&self,
bounds: &graphene::Rect,
gradient_bounds: &graphene::Rect,
) -> gsk::GLShaderNode {
) -> gsk::RenderNode {
self.imp()
.gradient_shader_node(bounds, gradient_bounds, &self.imp().bg_colors.borrow())
.fill_node(bounds, gradient_bounds, &self.imp().bg_colors.borrow())
}

pub 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(),
)
) -> gsk::RenderNode {
self.imp()
.fill_node(bounds, gradient_bounds, &self.imp().message_colors.borrow())
}

fn ensure_shader(&self) {
Expand All @@ -403,6 +448,23 @@ impl Background {
}
};
}

fn refresh_theme(&self, dark: bool) {
if let Some(chat_theme) = &*self.imp().chat_theme.borrow() {
let theme = if dark {
&chat_theme.dark_settings
} else {
&chat_theme.light_settings
};

self.set_theme(theme);

// For some reason tdlib tells that light theme is dark
self.imp().dark.set(dark);
} else {
self.set_theme(&hard_coded_themes(dark));
}
}
}

impl Default for Background {
Expand All @@ -411,22 +473,30 @@ impl Default for Background {
}
}

fn fill_colors(fill: tdlib::enums::BackgroundFill) -> Vec<graphene::Vec3> {
fn fill_colors(fill: &tdlib::enums::BackgroundFill) -> Vec<graphene::Vec3> {
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()
gradient.colors.iter().map(int_color_to_vec3).collect()
}
tdlib::enums::BackgroundFill::Solid(solid) => vec![int_color_to_vec3(&solid.color)],
tdlib::enums::BackgroundFill::Gradient(gradient) => vec![
int_color_to_vec3(&gradient.top_color),
int_color_to_vec3(&gradient.bottom_color),
],
_ => unimplemented!("Unsupported background fill: {fill:?}"),
}
}

fn int_color_to_vec3(color: &i32) -> graphene::Vec3 {
let [_, r, g, b] = color.to_be_bytes();
graphene::Vec3::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0)
}

fn vec3_to_rgba(vec3: &graphene::Vec3) -> gdk::RGBA {
let [red, green, blue] = vec3.to_float();
gdk::RGBA::new(red, green, blue, 1.0)
}

fn hard_coded_themes(dark: bool) -> tdlib::types::ThemeSettings {
fn theme(
dark: bool,
Expand Down
43 changes: 37 additions & 6 deletions src/session/content/chat_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ mod imp {
pub(crate) struct ChatHistory {
pub(super) compact: Cell<bool>,
pub(super) chat: RefCell<Option<Chat>>,
pub(super) chat_handler: RefCell<Option<glib::SignalHandlerId>>,
pub(super) chat_handlers: RefCell<Vec<glib::SignalHandlerId>>,
pub(super) session_handlers: RefCell<Vec<glib::SignalHandlerId>>,
pub(super) model: RefCell<Option<ChatHistoryModel>>,
pub(super) message_menu: OnceCell<gtk::PopoverMenu>,
pub(super) is_auto_scrolling: Cell<bool>,
Expand Down Expand Up @@ -357,13 +358,43 @@ impl ChatHistory {
}
}));

let handler = chat.connect_new_message(clone!(@weak self as obj => move |_, msg| {
if msg.is_outgoing() {
obj.imp().background.animate();
self.imp().background.set_chat_theme(chat.chat_theme());

let new_message_handler =
chat.connect_new_message(clone!(@weak self as obj => move |_, msg| {
if msg.is_outgoing() {
obj.imp().background.animate();
}
}));

let chat_theme_handler = chat.connect_notify_local(
Some("theme-name"),
clone!(@weak self as obj => move |chat, _| {
obj.imp().background.set_chat_theme(chat.chat_theme());
}),
);

for old_handler in self
.imp()
.chat_handlers
.replace(vec![new_message_handler, chat_theme_handler])
{
if let Some(old_chat) = &*imp.chat.borrow() {
old_chat.disconnect(old_handler);
}
}));
}

if let Some(old_handler) = self.imp().chat_handler.replace(Some(handler)) {
let chat_themes_handler = chat.session().connect_update_chat_themes(
clone!(@weak self as obj, @weak chat => move || {
obj.imp().background.set_chat_theme(chat.chat_theme());
}),
);

for old_handler in self
.imp()
.session_handlers
.replace(vec![chat_themes_handler])
{
if let Some(old_chat) = &*imp.chat.borrow() {
old_chat.disconnect(old_handler);
}
Expand Down
32 changes: 32 additions & 0 deletions src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::collections::hash_map::HashMap;

use adw::subclass::prelude::BinImpl;
use glib::clone;
use glib::subclass::Signal;
use glib::Sender;
use gtk::glib;
use gtk::glib::WeakRef;
Expand Down Expand Up @@ -59,6 +60,7 @@ mod imp {
pub(super) archive_chat_list: OnceCell<ChatList>,
pub(super) folder_chat_lists: RefCell<HashMap<i32, ChatList>>,
pub(super) chats: RefCell<HashMap<i64, Chat>>,
pub(super) chat_themes: RefCell<Vec<tdlib::types::ChatTheme>>,
pub(super) users: RefCell<HashMap<i64, User>>,
pub(super) basic_groups: RefCell<HashMap<i64, BasicGroup>>,
pub(super) supergroups: RefCell<HashMap<i64, Supergroup>>,
Expand Down Expand Up @@ -154,6 +156,12 @@ mod imp {
PROPERTIES.as_ref()
}

fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> =
Lazy::new(|| vec![Signal::builder("update-chat-themes").build()]);
SIGNALS.as_ref()
}

fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"client-id" => {
Expand Down Expand Up @@ -227,6 +235,7 @@ impl Session {
}
Update::ChatTitle(ref data) => self.chat(data.chat_id).handle_update(update),
Update::ChatPhoto(ref data) => self.chat(data.chat_id).handle_update(update),
Update::ChatTheme(ref data) => self.chat(data.chat_id).handle_update(update),
Update::ChatPermissions(ref data) => self.chat(data.chat_id).handle_update(update),
Update::ChatLastMessage(ref data) => {
let chat = self.chat(data.chat_id);
Expand Down Expand Up @@ -338,6 +347,10 @@ impl Session {
Update::UserStatus(data) => {
self.user(data.user_id).update_status(data.status);
}
Update::ChatThemes(chat_themes) => {
self.imp().chat_themes.replace(chat_themes.chat_themes);
self.emit_by_name::<()>("update-chat-themes", &[]);
}
_ => {}
}
}
Expand Down Expand Up @@ -510,6 +523,25 @@ impl Session {
}
}

pub(crate) fn find_chat_theme(&self, name: &str) -> Option<tdlib::types::ChatTheme> {
self.imp()
.chat_themes
.borrow()
.iter()
.find(|theme| theme.name == name)
.cloned()
}

pub(crate) fn connect_update_chat_themes<F>(&self, callback: F) -> glib::SignalHandlerId
where
F: Fn() + 'static,
{
self.connect_local("update-chat-themes", true, move |_| {
callback();
None
})
}

pub(crate) fn handle_paste_action(&self) {
self.imp().content.handle_paste_action();
}
Expand Down
Loading

0 comments on commit e780d7b

Please sign in to comment.