feat: private messages.

This commit is contained in:
Evan Carroll 2026-01-18 15:28:13 -06:00
parent 0492043625
commit 22cc0fdc38
11 changed files with 1135 additions and 44 deletions

View file

@ -81,6 +81,11 @@ impl ContentBounds {
fn empty_bottom_rows(&self) -> usize {
2 - self.max_row
}
/// Number of empty rows at the top (for bubble positioning).
fn empty_top_rows(&self) -> usize {
self.min_row
}
}
/// Get a unique key for a member (for Leptos For keying).
@ -386,7 +391,17 @@ pub fn AvatarCanvas(
if let Some(ref b) = bubble {
let current_time = js_sys::Date::now() as i64;
if b.expires_at >= current_time {
draw_bubble(&ctx, b, avatar_cx, avatar_cy - avatar_size / 2.0, avatar_size, te);
let content_x_offset = content_bounds.x_offset(cell_size);
let content_top_adjustment = content_bounds.empty_top_rows() as f64 * cell_size;
draw_bubble(
&ctx,
b,
avatar_cx,
avatar_cy - avatar_size / 2.0,
content_x_offset,
content_top_adjustment,
te,
);
}
}
});
@ -426,7 +441,8 @@ fn draw_bubble(
bubble: &ActiveBubble,
center_x: f64,
top_y: f64,
_prop_size: f64,
content_x_offset: f64,
content_top_adjustment: f64,
text_em_size: f64,
) {
// Text scale independent of zoom - only affected by user's text_em_size setting
@ -440,8 +456,11 @@ fn draw_bubble(
let (bg_color, border_color, text_color) = emotion_bubble_colors(&bubble.message.emotion);
// Use italic font for whispers
let font_style = if bubble.message.is_whisper { "italic " } else { "" };
// Measure and wrap text
ctx.set_font(&format!("{}px sans-serif", font_size));
ctx.set_font(&format!("{}{}px sans-serif", font_style, font_size));
let lines = wrap_text(ctx, &bubble.message.content, max_bubble_width - padding * 2.0);
// Calculate bubble dimensions
@ -453,9 +472,13 @@ fn draw_bubble(
let bubble_width = bubble_width.max(60.0 * text_scale);
let bubble_height = (lines.len() as f64) * line_height + padding * 2.0;
// Position bubble above avatar
let bubble_x = center_x - bubble_width / 2.0;
let bubble_y = top_y - bubble_height - tail_size - 5.0 * text_scale;
// Center bubble horizontally on content (not grid center)
let content_center_x = center_x + content_x_offset;
let bubble_x = content_center_x - bubble_width / 2.0;
// Position vertically closer to content when top rows are empty
let adjusted_top_y = top_y + content_top_adjustment;
let bubble_y = adjusted_top_y - bubble_height - tail_size - 5.0 * text_scale;
// Draw bubble background
draw_rounded_rect(ctx, bubble_x, bubble_y, bubble_width, bubble_height, border_radius);
@ -465,18 +488,19 @@ fn draw_bubble(
ctx.set_line_width(2.0);
ctx.stroke();
// Draw tail
// Draw tail pointing to content center
ctx.begin_path();
ctx.move_to(center_x - tail_size, bubble_y + bubble_height);
ctx.line_to(center_x, bubble_y + bubble_height + tail_size);
ctx.line_to(center_x + tail_size, bubble_y + bubble_height);
ctx.move_to(content_center_x - tail_size, bubble_y + bubble_height);
ctx.line_to(content_center_x, bubble_y + bubble_height + tail_size);
ctx.line_to(content_center_x + tail_size, bubble_y + bubble_height);
ctx.close_path();
ctx.set_fill_style_str(bg_color);
ctx.fill();
ctx.set_stroke_style_str(border_color);
ctx.stroke();
// Draw text
// Draw text (re-set font in case canvas state changed)
ctx.set_font(&format!("{}{}px sans-serif", font_style, font_size));
ctx.set_fill_style_str(text_color);
ctx.set_text_align("left");
ctx.set_text_baseline("top");