avatar fixes and implementation to edit
This commit is contained in:
parent
acab2f017d
commit
c3320ddcce
11 changed files with 1417 additions and 37 deletions
|
|
@ -51,13 +51,45 @@ pub fn AvatarCanvas(
|
|||
|
||||
// Clone data for use in closures
|
||||
let skin_layer = member.avatar.skin_layer.clone();
|
||||
let clothes_layer = member.avatar.clothes_layer.clone();
|
||||
let accessories_layer = member.avatar.accessories_layer.clone();
|
||||
let emotion_layer = member.avatar.emotion_layer.clone();
|
||||
let display_name = member.member.display_name.clone();
|
||||
let current_emotion = member.member.current_emotion;
|
||||
|
||||
// Calculate canvas position from scene coordinates
|
||||
let canvas_x = member.member.position_x * scale_x + offset_x - prop_size / 2.0;
|
||||
let canvas_y = member.member.position_y * scale_y + offset_y - prop_size;
|
||||
// Helper to check if any layer has content at a position
|
||||
let has_content_at = |pos: usize| -> bool {
|
||||
skin_layer[pos].is_some()
|
||||
|| clothes_layer[pos].is_some()
|
||||
|| accessories_layer[pos].is_some()
|
||||
|| emotion_layer[pos].is_some()
|
||||
};
|
||||
|
||||
// Calculate content bounds for positioning
|
||||
// X-axis: which columns have content
|
||||
let left_col_has_content = [0, 3, 6].iter().any(|&p| has_content_at(p));
|
||||
let middle_col_has_content = [1, 4, 7].iter().any(|&p| has_content_at(p));
|
||||
let right_col_has_content = [2, 5, 8].iter().any(|&p| has_content_at(p));
|
||||
|
||||
let min_col = if left_col_has_content { 0 } else if middle_col_has_content { 1 } else { 2 };
|
||||
let max_col = if right_col_has_content { 2 } else if middle_col_has_content { 1 } else { 0 };
|
||||
let content_center_col = (min_col + max_col) as f64 / 2.0;
|
||||
let x_content_offset = (content_center_col - 1.0) * prop_size;
|
||||
|
||||
// Y-axis: which rows have content
|
||||
let bottom_row_has_content = [6, 7, 8].iter().any(|&p| has_content_at(p));
|
||||
let middle_row_has_content = [3, 4, 5].iter().any(|&p| has_content_at(p));
|
||||
|
||||
let max_row = if bottom_row_has_content { 2 } else if middle_row_has_content { 1 } else { 0 };
|
||||
let empty_bottom_rows = 2 - max_row;
|
||||
let y_content_offset = empty_bottom_rows as f64 * prop_size;
|
||||
|
||||
// Avatar is a 3x3 grid of props, each prop is prop_size
|
||||
let avatar_size = prop_size * 3.0;
|
||||
|
||||
// Calculate canvas position from scene coordinates, adjusted for content bounds
|
||||
let canvas_x = member.member.position_x * scale_x + offset_x - avatar_size / 2.0 - x_content_offset * scale_x;
|
||||
let canvas_y = member.member.position_y * scale_y + offset_y - avatar_size + y_content_offset * scale_y;
|
||||
|
||||
// Fixed text dimensions (independent of prop_size/zoom)
|
||||
// Text stays readable regardless of zoom level - only affected by text_em_size slider
|
||||
|
|
@ -72,8 +104,8 @@ pub fn AvatarCanvas(
|
|||
let fixed_text_width = 200.0 * text_scale;
|
||||
|
||||
// Canvas must fit both avatar AND fixed-size text
|
||||
let canvas_width = prop_size.max(fixed_text_width);
|
||||
let canvas_height = prop_size + fixed_bubble_height + fixed_name_height;
|
||||
let canvas_width = avatar_size.max(fixed_text_width);
|
||||
let canvas_height = avatar_size + fixed_bubble_height + fixed_name_height;
|
||||
|
||||
// Adjust bubble_extra for positioning (used later in avatar_cy calculation)
|
||||
let bubble_extra = fixed_bubble_height;
|
||||
|
|
@ -90,7 +122,7 @@ pub fn AvatarCanvas(
|
|||
pointer-events: auto; \
|
||||
width: {}px; \
|
||||
height: {}px;",
|
||||
canvas_x - (canvas_width - prop_size) / 2.0,
|
||||
canvas_x - (canvas_width - avatar_size) / 2.0,
|
||||
adjusted_y,
|
||||
z_index,
|
||||
canvas_width,
|
||||
|
|
@ -115,6 +147,8 @@ pub fn AvatarCanvas(
|
|||
|
||||
// Clone values for the effect
|
||||
let skin_layer_clone = skin_layer.clone();
|
||||
let clothes_layer_clone = clothes_layer.clone();
|
||||
let accessories_layer_clone = accessories_layer.clone();
|
||||
let emotion_layer_clone = emotion_layer.clone();
|
||||
let display_name_clone = display_name.clone();
|
||||
let active_bubble_clone = active_bubble.clone();
|
||||
|
|
@ -143,19 +177,7 @@ pub fn AvatarCanvas(
|
|||
|
||||
// Avatar center position within the canvas
|
||||
let avatar_cx = canvas_width / 2.0;
|
||||
let avatar_cy = bubble_extra + prop_size / 2.0;
|
||||
|
||||
// Draw placeholder circle
|
||||
ctx.begin_path();
|
||||
let _ = ctx.arc(
|
||||
avatar_cx,
|
||||
avatar_cy,
|
||||
prop_size / 2.0,
|
||||
0.0,
|
||||
std::f64::consts::PI * 2.0,
|
||||
);
|
||||
ctx.set_fill_style_str("#6366f1");
|
||||
ctx.fill();
|
||||
let avatar_cy = bubble_extra + avatar_size / 2.0;
|
||||
|
||||
// Helper to load and draw an image
|
||||
// Images are cached; when loaded, triggers a redraw via signal
|
||||
|
|
@ -192,14 +214,58 @@ pub fn AvatarCanvas(
|
|||
}
|
||||
};
|
||||
|
||||
// Draw skin layer (position 4 = center)
|
||||
if let Some(ref skin_path) = skin_layer_clone[4] {
|
||||
draw_image(skin_path, &image_cache, &ctx, avatar_cx, avatar_cy, prop_size);
|
||||
// Draw all 9 positions of the avatar grid (3x3 layout)
|
||||
// Grid positions:
|
||||
// 0 1 2
|
||||
// 3 4 5
|
||||
// 6 7 8
|
||||
// Each cell is full prop_size, grid is 3x3
|
||||
let cell_size = prop_size;
|
||||
let grid_origin_x = avatar_cx - avatar_size / 2.0;
|
||||
let grid_origin_y = avatar_cy - avatar_size / 2.0;
|
||||
|
||||
// Draw skin layer for all 9 positions
|
||||
for pos in 0..9 {
|
||||
if let Some(ref skin_path) = skin_layer_clone[pos] {
|
||||
let col = pos % 3;
|
||||
let row = pos / 3;
|
||||
let cell_cx = grid_origin_x + (col as f64 + 0.5) * cell_size;
|
||||
let cell_cy = grid_origin_y + (row as f64 + 0.5) * cell_size;
|
||||
draw_image(skin_path, &image_cache, &ctx, cell_cx, cell_cy, cell_size);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw emotion overlay (position 4 = center)
|
||||
if let Some(ref emotion_path) = emotion_layer_clone[4] {
|
||||
draw_image(emotion_path, &image_cache, &ctx, avatar_cx, avatar_cy, prop_size);
|
||||
// Draw clothes layer for all 9 positions
|
||||
for pos in 0..9 {
|
||||
if let Some(ref clothes_path) = clothes_layer_clone[pos] {
|
||||
let col = pos % 3;
|
||||
let row = pos / 3;
|
||||
let cell_cx = grid_origin_x + (col as f64 + 0.5) * cell_size;
|
||||
let cell_cy = grid_origin_y + (row as f64 + 0.5) * cell_size;
|
||||
draw_image(clothes_path, &image_cache, &ctx, cell_cx, cell_cy, cell_size);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw accessories layer for all 9 positions
|
||||
for pos in 0..9 {
|
||||
if let Some(ref accessories_path) = accessories_layer_clone[pos] {
|
||||
let col = pos % 3;
|
||||
let row = pos / 3;
|
||||
let cell_cx = grid_origin_x + (col as f64 + 0.5) * cell_size;
|
||||
let cell_cy = grid_origin_y + (row as f64 + 0.5) * cell_size;
|
||||
draw_image(accessories_path, &image_cache, &ctx, cell_cx, cell_cy, cell_size);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw emotion overlay for all 9 positions
|
||||
for pos in 0..9 {
|
||||
if let Some(ref emotion_path) = emotion_layer_clone[pos] {
|
||||
let col = pos % 3;
|
||||
let row = pos / 3;
|
||||
let cell_cx = grid_origin_x + (col as f64 + 0.5) * cell_size;
|
||||
let cell_cy = grid_origin_y + (row as f64 + 0.5) * cell_size;
|
||||
draw_image(emotion_path, &image_cache, &ctx, cell_cx, cell_cy, cell_size);
|
||||
}
|
||||
}
|
||||
|
||||
// Text scale independent of zoom - only affected by user's text_em_size setting
|
||||
|
|
@ -208,8 +274,8 @@ pub fn AvatarCanvas(
|
|||
// Draw emotion badge if non-neutral
|
||||
if current_emotion > 0 {
|
||||
let badge_size = 16.0 * text_scale;
|
||||
let badge_x = avatar_cx + prop_size / 2.0 - badge_size / 2.0;
|
||||
let badge_y = avatar_cy - prop_size / 2.0 - badge_size / 2.0;
|
||||
let badge_x = avatar_cx + avatar_size / 2.0 - badge_size / 2.0;
|
||||
let badge_y = avatar_cy - avatar_size / 2.0 - badge_size / 2.0;
|
||||
|
||||
ctx.begin_path();
|
||||
let _ = ctx.arc(badge_x, badge_y, badge_size / 2.0, 0.0, std::f64::consts::PI * 2.0);
|
||||
|
|
@ -223,24 +289,63 @@ pub fn AvatarCanvas(
|
|||
let _ = ctx.fill_text(&format!("{}", current_emotion), badge_x, badge_y);
|
||||
}
|
||||
|
||||
// Helper to check if any layer has content at a position
|
||||
let has_content_at = |pos: usize| -> bool {
|
||||
skin_layer_clone[pos].is_some()
|
||||
|| clothes_layer_clone[pos].is_some()
|
||||
|| accessories_layer_clone[pos].is_some()
|
||||
|| emotion_layer_clone[pos].is_some()
|
||||
};
|
||||
|
||||
// Calculate empty bottom rows to adjust name Y position
|
||||
let mut empty_bottom_rows = 0;
|
||||
|
||||
// Check row 2 (positions 6, 7, 8)
|
||||
let row2_has_content = (6..=8).any(&has_content_at);
|
||||
if !row2_has_content {
|
||||
empty_bottom_rows += 1;
|
||||
// Check row 1 (positions 3, 4, 5)
|
||||
let row1_has_content = (3..=5).any(&has_content_at);
|
||||
if !row1_has_content {
|
||||
empty_bottom_rows += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate X offset to center name on columns with content
|
||||
// Column 0 (left): positions 0, 3, 6
|
||||
// Column 1 (middle): positions 1, 4, 7
|
||||
// Column 2 (right): positions 2, 5, 8
|
||||
let left_col_has_content = [0, 3, 6].iter().any(|&p| has_content_at(p));
|
||||
let middle_col_has_content = [1, 4, 7].iter().any(|&p| has_content_at(p));
|
||||
let right_col_has_content = [2, 5, 8].iter().any(|&p| has_content_at(p));
|
||||
|
||||
// Find leftmost and rightmost columns with content
|
||||
let min_col = if left_col_has_content { 0 } else if middle_col_has_content { 1 } else { 2 };
|
||||
let max_col = if right_col_has_content { 2 } else if middle_col_has_content { 1 } else { 0 };
|
||||
|
||||
// Calculate center of content columns (grid center is column 1)
|
||||
let content_center_col = (min_col + max_col) as f64 / 2.0;
|
||||
let x_offset = (content_center_col - 1.0) * cell_size;
|
||||
let name_x = avatar_cx + x_offset;
|
||||
|
||||
// Draw display name below avatar (with black outline for readability)
|
||||
ctx.set_font(&format!("{}px sans-serif", 12.0 * text_scale));
|
||||
ctx.set_text_align("center");
|
||||
ctx.set_text_baseline("alphabetic");
|
||||
let name_y = avatar_cy + prop_size / 2.0 + 15.0 * text_scale;
|
||||
let name_y = avatar_cy + avatar_size / 2.0 - (empty_bottom_rows as f64 * cell_size) + 15.0 * text_scale;
|
||||
// Black outline
|
||||
ctx.set_stroke_style_str("#000");
|
||||
ctx.set_line_width(3.0);
|
||||
let _ = ctx.stroke_text(&display_name_clone, avatar_cx, name_y);
|
||||
let _ = ctx.stroke_text(&display_name_clone, name_x, name_y);
|
||||
// White fill
|
||||
ctx.set_fill_style_str("#fff");
|
||||
let _ = ctx.fill_text(&display_name_clone, avatar_cx, name_y);
|
||||
let _ = ctx.fill_text(&display_name_clone, name_x, name_y);
|
||||
|
||||
// Draw speech bubble if active
|
||||
if let Some(ref bubble) = active_bubble_clone {
|
||||
let current_time = js_sys::Date::now() as i64;
|
||||
if bubble.expires_at >= current_time {
|
||||
draw_bubble(&ctx, bubble, avatar_cx, avatar_cy - prop_size / 2.0, prop_size, text_em_size);
|
||||
draw_bubble(&ctx, bubble, avatar_cx, avatar_cy - avatar_size / 2.0, avatar_size, text_em_size);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue