Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use interactive buttons to display and send (toggle) reactions #168

Merged
merged 79 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
1453a13
emoji
alanpoon Sep 27, 2024
5d13dfc
emo
alanpoon Sep 29, 2024
d77281a
reaction_list
alanpoon Oct 1, 2024
20b4f0d
added emoji toggle
alanpoon Oct 1, 2024
ad3b14d
minor cleanup
alanpoon Oct 1, 2024
632eaa7
Merge branch 'main' into interactive_icon_button_#115
alanpoon Oct 2, 2024
0d0dc06
removed message annotation template
alanpoon Oct 2, 2024
c26719f
Merge branch 'main' into interactive_icon_button_#115
alanpoon Oct 3, 2024
f9bbb9f
resolve conflict
alanpoon Oct 3, 2024
39c7c58
commiting cargo.lock
alanpoon Oct 3, 2024
4f818a3
revert use main's cargo.lock
alanpoon Oct 3, 2024
3f5500c
did some formating
alanpoon Oct 4, 2024
d3a0b29
Merge branch 'main' into interactive_icon_button_#115
alanpoon Oct 11, 2024
0db2482
resolve minor issue
alanpoon Oct 11, 2024
926871d
Merge branch 'main' into interactive_icon_button_#115
alanpoon Oct 12, 2024
e585205
resolve conflict
alanpoon Oct 12, 2024
0279b2e
merge main n resolve conflict
alanpoon Oct 12, 2024
b02c4ce
Merge branch 'main' into interactive_icon_button_#115
alanpoon Oct 30, 2024
bda02cf
Merge branch 'main' of https://github.com/project-robius/robrix into …
alanpoon Nov 27, 2024
867a455
ReactionButton hide before width calculated
alanpoon Nov 28, 2024
d4efb31
Merge branch 'main' of https://github.com/project-robius/robrix into …
alanpoon Nov 28, 2024
48f22b5
resolve conflict
alanpoon Nov 28, 2024
ac13516
removed unneccessary changes
alanpoon Nov 28, 2024
1b31846
remove redraw after set_list
alanpoon Nov 28, 2024
5616b4a
Merge branch 'main' of https://github.com/project-robius/robrix into …
alanpoon Dec 3, 2024
23c74dd
fixed spelling mistake
alanpoon Dec 3, 2024
3e4ae95
fix clippy
alanpoon Dec 3, 2024
af313fb
increase padding to 5
alanpoon Dec 4, 2024
52c2b01
Added tooltip for interactive button
alanpoon Dec 6, 2024
34f1543
fix clippy
alanpoon Dec 6, 2024
d6c2bfd
Added display_name from user profile cache in tooltip
alanpoon Dec 9, 2024
dc1c4ad
changed to map
alanpoon Dec 9, 2024
33a2437
Removed tuple and slight improve in tooltip cutoff
alanpoon Dec 10, 2024
bc19f96
consistent roomscreentooltipActions
alanpoon Dec 10, 2024
70ed74c
minor change
alanpoon Dec 10, 2024
49425bb
Update src/sliding_sync.rs
alanpoon Dec 18, 2024
2be0b94
changing wording for reaction_key
alanpoon Dec 18, 2024
f3bd019
removed clientuser_id in timelinestate
alanpoon Dec 23, 2024
82e5efc
Merge branch 'main' into interactive_icon_button_#115
alanpoon Dec 23, 2024
bf1e2cf
fix tooltip
alanpoon Dec 23, 2024
b369397
Added callout tooltip widget
alanpoon Dec 23, 2024
a8ad340
remove temporary hack for tooltip
alanpoon Dec 23, 2024
21a5313
Added displayname for reaction tooltip
alanpoon Dec 25, 2024
742f1f4
Merge branch 'main' of https://github.com/project-robius/robrix into …
alanpoon Dec 31, 2024
ac2ffcb
fix tooltip overlap
alanpoon Dec 31, 2024
4693920
tooltip cleanup
alanpoon Jan 1, 2025
16f204c
remove Option for callout_y_offset
alanpoon Jan 1, 2025
8238397
Fix Doc
alanpoon Jan 2, 2025
ce504c9
Merge branch 'main' of https://github.com/project-robius/robrix into …
alanpoon Jan 2, 2025
b261b5d
use rightwrap
alanpoon Jan 3, 2025
84d5e51
remove hide for first draw in event_reaction_list
alanpoon Jan 3, 2025
6826070
Merge branch 'main' of https://github.com/project-robius/robrix into …
alanpoon Jan 3, 2025
e195ec5
remove width_calculated
alanpoon Jan 3, 2025
e8efce0
Flow:RightWrap
alanpoon Jan 4, 2025
4ca7219
margin remove parenthese
alanpoon Jan 4, 2025
211b375
format margin
alanpoon Jan 4, 2025
be4d20b
format bg_color
alanpoon Jan 4, 2025
f9709eb
early return
alanpoon Jan 4, 2025
5dcebf3
format long function line
alanpoon Jan 4, 2025
5f85166
simplifed reaction list
alanpoon Jan 7, 2025
7090dc9
restore MatrixRequest::CheckCanUserSendMessage
alanpoon Jan 7, 2025
69a16c4
doc formating
alanpoon Jan 13, 2025
c7a5045
Populate tooltip text after mouseover in reaction buttons
alanpoon Jan 13, 2025
6b54d76
merge main
alanpoon Jan 13, 2025
8bc1470
remove debugging
alanpoon Jan 13, 2025
0997f40
revert changes
alanpoon Jan 13, 2025
f29683c
Merge remote-tracking branch 'origin' into interactive_icon_button_#115
alanpoon Jan 13, 2025
368deff
Change to mouse position detection for message body
alanpoon Jan 13, 2025
c0fddf5
revert handle_event for Message
alanpoon Jan 14, 2025
fbc3ab1
Merge branch 'main' into interactive_icon_button_#115
alanpoon Jan 16, 2025
d99ac95
fix doc comment
alanpoon Jan 16, 2025
0a3118b
Fix half screening reaction button styling
alanpoon Jan 17, 2025
b5f2b93
improving tooltip clearing
alanpoon Jan 17, 2025
b39631f
Merge branch 'main' into interactive_icon_button_#115
alanpoon Jan 17, 2025
05e7d0e
fix edge toggle reaction
alanpoon Jan 17, 2025
f89ef19
Display tooltip at the bottom if the reaction button is too close to …
alanpoon Jan 19, 2025
0229149
align callout to the first line
alanpoon Jan 20, 2025
0fd23c3
Enlarge reaction font size. Left-align reaction buttons.
kevinaboos Jan 20, 2025
6189c44
Merge branch 'main' into interactive_icon_button_#115
kevinaboos Jan 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
371 changes: 371 additions & 0 deletions src/home/event_reaction_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,371 @@
use crate::sliding_sync::{get_client, submit_async_request, MatrixRequest};
use crate::utils::human_readable_list;
use makepad_widgets::*;
use matrix_sdk::ruma::OwnedRoomId;
use matrix_sdk_ui::timeline::{ReactionsByKeyBySender, TimelineEventItemId};
use crate::profile::user_profile_cache::get_user_profile;
use crate::home::room_screen::RoomScreenTooltipActions;

use super::room_screen::HoverInData;
const TOOLTIP_WIDTH: f64 = 100.0;
const REACTION_LIST_PADDING_RIGHT: f64 = 3.0;
live_design! {
use link::theme::*;
use link::shaders::*;
use link::widgets::*;

use crate::shared::styles::*;
COLOR_BUTTON_GREY = #B6BABF
REACTION_LIST_PADDING_RIGHT = 30.0;
pub ReactionList = {{ReactionList}} {
width: Fill,
height: Fit,
margin: {top: (5.0)}
alanpoon marked this conversation as resolved.
Show resolved Hide resolved
padding:{
right: (REACTION_LIST_PADDING_RIGHT)
}
item: <Button> {
width: Fit,
height: Fit,
spacing: 20,
kevinaboos marked this conversation as resolved.
Show resolved Hide resolved
padding: 6,
margin: {
top:3,
bottom:3,
left:3,
right:3

},
alanpoon marked this conversation as resolved.
Show resolved Hide resolved
draw_bg: {
instance color: (COLOR_BUTTON_GREY)
instance color_hover: (#fef65b)
kevinaboos marked this conversation as resolved.
Show resolved Hide resolved
instance border_width: 1.5
instance border_color: (#001A11)
kevinaboos marked this conversation as resolved.
Show resolved Hide resolved
instance radius: 3.0
// The first draw is to get the width of the button, so that we can use it in the second draw
// If hide >= 0.5, the button is hidden.
// Without hiding, the buttons layout may appear glitched at the start
instance hide: 0.0
fn get_color(self) -> vec4 {
return mix(self.color, mix(self.color, self.color_hover, 0.2), self.hover)
}

fn pixel(self) -> vec4 {
let sdf = Sdf2d::viewport(self.pos * self.rect_size)
if self.hide >= 0.5 {
return sdf.result;
}
sdf.box(
self.border_width,
self.border_width,
self.rect_size.x - (self.border_width * 2.0),
self.rect_size.y - (self.border_width * 2.0),
max(1.0, self.radius)
)
sdf.fill_keep(self.get_color())
if self.border_width > 0.0 {
let stroke_color = mix(self.get_color(), self.border_color, 0.2);
sdf.stroke(stroke_color, self.border_width)
}
return sdf.result;
}
}
draw_text: {
text_style: <REGULAR_TEXT>{font_size: 8},
color: #000
fn get_color(self) -> vec4 {
return self.color;
}
}
}
}

}
struct ReactionData {
emoji: String,
/// Total number of people reacted to the emoji
total_num_react: usize,
/// Tooltip text display when mouse over the reaction button
tooltip_text: String,
/// Boolean indicating if the current user is also a sender of the reaction
includes_user: bool,
/// Calculated of the width of the reaction button
width: f64,
}

#[derive(Live, LiveHook, Widget)]
pub struct ReactionList {
#[redraw]
#[rust]
area: Area,
#[live]
item: Option<LivePtr>,
#[rust]
children: ComponentMap<LiveId, ButtonRef>,
#[layout]
layout: Layout,
#[walk]
walk: Walk,
/// A list of ReactionData which includes data required to draw the reaction buttons and their tooltips
/// After the first draw, the button widths will be stored in this vector
#[rust]
event_reaction_list: Vec<ReactionData>,
#[rust]
room_id: Option<OwnedRoomId>,
#[rust]
timeline_event_id: Option<TimelineEventItemId>,
/// Has the width of the emoji buttons already been drawn and calculated beforehand?
#[rust]
width_calculated: bool,
/// Tooltip that appears when hovering over a reaction button, (Index in event_reaction_list, tooltip rendering rectangle's area, tooltip's text, callout's y offset)
#[rust]
tooltip_state: Option<(u64, HoverInData)>
}
impl Widget for ReactionList {
fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
cx.begin_turtle(walk, self.layout);
let rect = cx.turtle().rect();
let width: f64 = rect.size.x;
if !self.width_calculated {
// Records the button widths after the first draw
let mut prev_width: f64 = 0.0;
for (index, reaction_data) in
self.event_reaction_list.iter_mut().enumerate()
{
let target = self.children.get_or_insert(cx, LiveId(index as u64), |cx| {
WidgetRef::new_from_ptr(cx, self.item).as_button()
});
target.set_text(&format!("{} {}", reaction_data.emoji, reaction_data.total_num_react));
// Hide the button until the first draw
target.apply_over(
cx,
live! {
draw_bg: { hide: 1.0 }
},
);
let _ = target.draw(cx, scope);
let used = cx.turtle().used();
reaction_data.width = used.x - prev_width;
prev_width = used.x;
}

self.width_calculated = true;
} else {
// With the width calculated from the first draw,
let mut acc_width: f64 = 0.0;
for (index, reaction_data) in
self.event_reaction_list.iter().enumerate()
{
let target = self.children.get_or_insert(cx, LiveId(index as u64), |cx| {
WidgetRef::new_from_ptr(cx, self.item).as_button()
});
target.set_text(&format!("{} {}", reaction_data.emoji, reaction_data.total_num_react));
// Renders Green button for reaction that includes the client user
// Renders Grey button for reaction that does not include client user
let node_to_apply = if reaction_data.includes_user {
live! {
draw_bg: { hide: 0.0 , color: (vec4(0.0, 0.6, 0.47, 1.0)) }
kevinaboos marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
live! {
draw_bg: { hide: 0.0, color: (vec4(0.714, 0.73, 0.75, 1.0)) }
kevinaboos marked this conversation as resolved.
Show resolved Hide resolved
}
};
// Unhide the button as we have the width of the buttons
target.apply_over(
cx,
node_to_apply
);
acc_width += reaction_data.width;
// Creates a new line if the accumulated width exceeds the available space
if acc_width > width {
cx.turtle_new_line();
acc_width = reaction_data.width;
let used: DVec2 = cx.turtle().used();
// Resets the turtle's width after each new line
cx.turtle_mut().set_used(0.0, used.y);
}
let _ = target.draw(cx, scope);
}
}

cx.end_turtle();
self.children.retain_visible();
DrawStep::done()
}
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
let Some(room_id) = &self.room_id else { return };
let Some(timeline_event_id) = &self.timeline_event_id else {
return;
};
// Apply mouse-in tooltip effect on the reaction buttons
// Currently handling mouse-in effect using "event.hits(cx, widget_ref.area())" does not work.
kevinaboos marked this conversation as resolved.
Show resolved Hide resolved
if let Event::MouseMove(e) = event {
let uid = self.widget_uid();
if self.tooltip_state.is_none() {
for (id, widget_ref) in self.children.iter() {
// Widget.handle_event here does not cause the button to be highlighted when mouse over
// To make the button highlighted when mouse over, the iteration over the children needs to be done
// outside Event::MouseMove.
let widget_rect = widget_ref.area().rect(cx);
if widget_rect.contains(e.abs) {
if let Some(reaction_data) = self.event_reaction_list.get(id.0 as usize) {
let rect = Rect {
pos: DVec2 {
x: widget_rect.pos.x + widget_rect.size.x - REACTION_LIST_PADDING_RIGHT,
y: widget_rect.pos.y - widget_rect.size.y / 2.0
},
size: DVec2::new(),
};
// Stores the event_reaction_list index together with the tooltip area and tooltip text into tooltip state
// The index will be used later to reset the tooltip state if the mouse leaves this particular reaction button
self.tooltip_state = Some((id.0, HoverInData {
tooltip_position: rect,
tooltip_width: TOOLTIP_WIDTH,
tooltip_text: reaction_data.tooltip_text.clone(),
callout_y_offset: (widget_rect.size.y - 5.0) / 2.0 + 10.0 //minus 5 because of top margin, + 10.0 because of tooltip's padding
}));
}
}
}
} else {
let mut reset_tooltip_state = false;
if let Some((ref index, hover_in_data)) = &self.tooltip_state {
self.children
.iter()
.for_each(|(id, widget_ref)| {
kevinaboos marked this conversation as resolved.
Show resolved Hide resolved
// Search for the children with the same index as the tooltip state and check if the mouse leaves this particular reaction button
// If so, post a HoverOut action to make the tooltip disable
if id.0 != *index {
return;
}
if !widget_ref.area().rect(cx).contains(e.abs) {
if self.event_reaction_list.get(id.0 as usize).is_some() {
reset_tooltip_state = true;
cx.widget_action(uid, &scope.path, RoomScreenTooltipActions::HoverOut);
}
}
});
// If the mouse does not leave this particular reaction button, post a HoverIn action
if !reset_tooltip_state {
cx.widget_action(uid, &scope.path, RoomScreenTooltipActions::HoverIn(hover_in_data.clone()));
}
}
if reset_tooltip_state {
self.tooltip_state = None;
}
}
}
if let Event::Actions(actions) = event {
self.children
.iter()
.for_each(|(_id, widget_ref)| {
kevinaboos marked this conversation as resolved.
Show resolved Hide resolved
if widget_ref.clicked(actions) {
let text = widget_ref.text().clone();
let reaction_string = text.rsplit_once(' ')
.map(|(prefix, _)| prefix)
.unwrap_or(&text);
if let Some(key) = emojis::get_by_shortcode(reaction_string) {
submit_async_request(MatrixRequest::ToggleReaction {
room_id: room_id.clone(),
timeline_event_id: timeline_event_id.clone(),
reaction: key.as_str().to_string(),
kevinaboos marked this conversation as resolved.
Show resolved Hide resolved
});
}
}
});
}


}
}

impl ReactionListRef {
/// Set the list of reactions and their counts to display in the ReactionList widget,
/// along with the room ID and event ID that these reactions are for.
///
/// This will clear any existing list of reactions and replace it with the given one.
///
/// The given `event_tl_item_reactions` is a map from each reaction's raw string (including any variant selectors)
/// to the list of users who have reacted with that reaction.
///
/// The given `room_id` is the ID of the room that these reactions are for.
///
/// The given `timeline_event_item_id` is the ID of the event that these reactions are for.
/// Required by Matrix API
pub fn set_list(
&mut self,
cx: &mut Cx,
event_tl_item_reactions: &ReactionsByKeyBySender,
room_id: OwnedRoomId,
timeline_event_item_id: TimelineEventItemId,
) {
let Some(client_user_id) = get_client().and_then(|c| c.user_id().map(|user_id| user_id.to_owned()) ) else { return };
kevinaboos marked this conversation as resolved.
Show resolved Hide resolved
if let Some(mut instance) = self.borrow_mut() {
alanpoon marked this conversation as resolved.
Show resolved Hide resolved
instance.event_reaction_list = Vec::with_capacity(event_tl_item_reactions.len());
for (reaction_raw, reaction_senders) in event_tl_item_reactions.iter() {
// Just take the first char of the emoji, which ignores any variant selectors.
let reaction_str_option = reaction_raw.chars().next().map(|c| c.to_string());
let reaction_str = reaction_str_option.as_deref().unwrap_or(reaction_raw);
let emoji_text = emojis::get(reaction_str)
.and_then(|e| e.shortcode())
.unwrap_or_else(|| {
log!("Failed to parse emoji: {}", reaction_raw);
reaction_raw
});
let total_num_react = reaction_senders.len();
let mut includes_user = false;
let mut user_id_list = Vec::with_capacity(5);
for (index, (sender, _react_info)) in reaction_senders.iter().enumerate() {
if sender == &client_user_id {
includes_user = true;
}
if index < 5 {
user_id_list.push(sender.clone());
}
}
let tooltip_text_arr:Vec<String> = reaction_senders.iter().map(|(sender, _react_info)|{
if sender == &client_user_id {
includes_user = true;
}
get_user_profile(cx, sender).map(|profile| profile.displayable_name().to_owned()).unwrap_or_else(||{
submit_async_request(MatrixRequest::GetUserProfile {
kevinaboos marked this conversation as resolved.
Show resolved Hide resolved
user_id: sender.to_owned(), room_id: Some(room_id.to_owned()), local_only: false
});
sender.to_string()
})
}).collect();
let mut tooltip_text = human_readable_list(&tooltip_text_arr);
tooltip_text.push_str(&format!("\nreacted with: {}", emoji_text));
instance.event_reaction_list.push(ReactionData{
emoji: emoji_text.to_string(),
total_num_react,
tooltip_text,
includes_user,
width: 0.0,
});
}
instance.room_id = Some(room_id);
instance.timeline_event_id = Some(timeline_event_item_id);
instance.width_calculated = false;
}
}
pub fn hover_in(&self, actions: &Actions) -> Option<HoverInData> {
if let Some(item) = actions.find_widget_action(self.widget_uid()) {
match item.cast() {
RoomScreenTooltipActions::HoverIn(hover_in_data) => Some(hover_in_data),
_ => None,
}
} else {
None
}
}
/// Handles hover out action
alanpoon marked this conversation as resolved.
Show resolved Hide resolved
pub fn hover_out(&self, actions: &Actions) -> bool {
if let Some(item) = actions.find_widget_action(self.widget_uid()) {
matches!(item.cast(), RoomScreenTooltipActions::HoverOut)
} else {
false
}
}
}
2 changes: 2 additions & 0 deletions src/home/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod rooms_list;
pub mod rooms_sidebar;
pub mod spaces_dock;
pub mod welcome_screen;
pub mod event_reaction_list;

pub fn live_design(cx: &mut Cx) {
home_screen::live_design(cx);
Expand All @@ -24,4 +25,5 @@ pub fn live_design(cx: &mut Cx) {
spaces_dock::live_design(cx);
welcome_screen::live_design(cx);
light_themed_dock::live_design(cx);
event_reaction_list::live_design(cx);
}
Loading
Loading