Make layer dragging graphics nicer
This commit is contained in:
parent
516960062a
commit
8d8f94a547
|
|
@ -327,6 +327,43 @@ fn flatten_layer<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Paint a soft drop shadow around a rect using gradient meshes (bottom + right + corner).
|
||||
/// Three non-overlapping quads so alpha doesn't double up.
|
||||
fn paint_drop_shadow(painter: &egui::Painter, rect: egui::Rect, shadow_size: f32, alpha: u8) {
|
||||
let c = egui::Color32::from_black_alpha(alpha);
|
||||
let t = egui::Color32::TRANSPARENT;
|
||||
let mut mesh = egui::Mesh::default();
|
||||
|
||||
// Bottom edge: straight down, stops at right edge
|
||||
let idx = mesh.vertices.len() as u32;
|
||||
mesh.colored_vertex(rect.left_bottom(), c); // 0
|
||||
mesh.colored_vertex(rect.right_bottom(), c); // 1
|
||||
mesh.colored_vertex(egui::pos2(rect.right(), rect.bottom() + shadow_size), t); // 2
|
||||
mesh.colored_vertex(egui::pos2(rect.left(), rect.bottom() + shadow_size), t); // 3
|
||||
mesh.add_triangle(idx, idx + 1, idx + 2);
|
||||
mesh.add_triangle(idx, idx + 2, idx + 3);
|
||||
|
||||
// Right edge: rightward, stops at bottom edge
|
||||
let idx = mesh.vertices.len() as u32;
|
||||
mesh.colored_vertex(rect.right_top(), c); // 0
|
||||
mesh.colored_vertex(egui::pos2(rect.right() + shadow_size, rect.top()), t); // 1
|
||||
mesh.colored_vertex(egui::pos2(rect.right() + shadow_size, rect.bottom()), t); // 2
|
||||
mesh.colored_vertex(rect.right_bottom(), c); // 3
|
||||
mesh.add_triangle(idx, idx + 1, idx + 2);
|
||||
mesh.add_triangle(idx, idx + 2, idx + 3);
|
||||
|
||||
// Bottom-right corner: dark at inner corner, transparent at other three
|
||||
let idx = mesh.vertices.len() as u32;
|
||||
mesh.colored_vertex(rect.right_bottom(), c); // 0
|
||||
mesh.colored_vertex(egui::pos2(rect.right() + shadow_size, rect.bottom()), t); // 1
|
||||
mesh.colored_vertex(egui::pos2(rect.right() + shadow_size, rect.bottom() + shadow_size), t); // 2
|
||||
mesh.colored_vertex(egui::pos2(rect.right(), rect.bottom() + shadow_size), t); // 3
|
||||
mesh.add_triangle(idx, idx + 1, idx + 2);
|
||||
mesh.add_triangle(idx, idx + 2, idx + 3);
|
||||
|
||||
painter.add(egui::Shape::mesh(mesh));
|
||||
}
|
||||
|
||||
/// Shift+click layer selection: toggle a layer in/out of the focus selection,
|
||||
/// enforcing the sibling constraint (all selected layers must share the same parent).
|
||||
fn shift_toggle_layer(
|
||||
|
|
@ -1604,9 +1641,8 @@ impl TimelinePane {
|
|||
egui::vec2(LAYER_HEADER_WIDTH, LAYER_HEIGHT),
|
||||
);
|
||||
|
||||
// Drop shadow (offset down-right, semi-transparent black)
|
||||
let shadow_rect = float_rect.translate(egui::vec2(3.0, 4.0));
|
||||
ui.painter().rect_filled(shadow_rect, 2.0, egui::Color32::from_black_alpha(80));
|
||||
// Gradient drop shadow
|
||||
paint_drop_shadow(ui.painter(), float_rect, 8.0, 60);
|
||||
|
||||
// Background (active/selected color)
|
||||
ui.painter().rect_filled(float_rect, 0.0, active_color);
|
||||
|
|
@ -1737,23 +1773,62 @@ impl TimelinePane {
|
|||
// Build virtual row list (accounts for group expansion)
|
||||
let all_rows = build_timeline_rows(context_layers);
|
||||
|
||||
// When dragging layers, filter them out and compute gap-adjusted positions
|
||||
// When dragging layers, compute remapped Y positions:
|
||||
// - Dragged rows render at the gap position
|
||||
// - Non-dragged rows shift around the gap
|
||||
let drag_layer_ids_content: Vec<uuid::Uuid> = self.layer_drag.as_ref()
|
||||
.map(|d| d.layer_ids.clone()).unwrap_or_default();
|
||||
let drag_count_content = drag_layer_ids_content.len();
|
||||
let gap_row_index_content = self.layer_drag.as_ref().map(|d| d.gap_row_index);
|
||||
|
||||
let rows: Vec<&TimelineRow> = all_rows.iter()
|
||||
.filter(|r| !drag_layer_ids_content.contains(&r.layer_id()))
|
||||
.collect();
|
||||
// Pre-compute Y position for each row.
|
||||
// Dragged rows follow the mouse continuously (matching the floating header);
|
||||
// non-dragged rows snap to discrete positions shifted around the gap.
|
||||
let drag_float_top_y: Option<f32> = self.layer_drag.as_ref()
|
||||
.map(|d| d.current_mouse_y - d.grab_offset_y);
|
||||
|
||||
// Draw layer rows from virtual row list
|
||||
for (filtered_i, row) in rows.iter().enumerate() {
|
||||
let visual_i = match gap_row_index_content {
|
||||
Some(gap) if filtered_i >= gap => filtered_i + drag_count_content,
|
||||
_ => filtered_i,
|
||||
};
|
||||
let y = rect.min.y + visual_i as f32 * LAYER_HEIGHT - self.viewport_scroll_y;
|
||||
let row_y_positions: Vec<f32> = {
|
||||
let mut positions = Vec::with_capacity(all_rows.len());
|
||||
let mut filtered_i = 0usize;
|
||||
let mut drag_offset = 0usize;
|
||||
for row in all_rows.iter() {
|
||||
if drag_layer_ids_content.contains(&row.layer_id()) {
|
||||
// Dragged row: continuous Y from mouse position
|
||||
let base_y = drag_float_top_y.unwrap_or(0.0);
|
||||
positions.push(base_y + drag_offset as f32 * LAYER_HEIGHT);
|
||||
drag_offset += 1;
|
||||
} else {
|
||||
// Non-dragged row: discrete position, shifted around gap
|
||||
let visual = match gap_row_index_content {
|
||||
Some(gap) if filtered_i >= gap => filtered_i + drag_count_content,
|
||||
_ => filtered_i,
|
||||
};
|
||||
positions.push(rect.min.y + visual as f32 * LAYER_HEIGHT - self.viewport_scroll_y);
|
||||
filtered_i += 1;
|
||||
}
|
||||
}
|
||||
positions
|
||||
};
|
||||
|
||||
// Draw non-dragged rows first, then dragged rows on top (so shadow/content overlaps correctly)
|
||||
let draw_order: Vec<usize> = {
|
||||
let mut non_dragged: Vec<usize> = Vec::new();
|
||||
let mut dragged: Vec<usize> = Vec::new();
|
||||
for (i, row) in all_rows.iter().enumerate() {
|
||||
if drag_layer_ids_content.contains(&row.layer_id()) {
|
||||
dragged.push(i);
|
||||
} else {
|
||||
non_dragged.push(i);
|
||||
}
|
||||
}
|
||||
non_dragged.extend(dragged);
|
||||
non_dragged
|
||||
};
|
||||
|
||||
for &i in &draw_order {
|
||||
let row = &all_rows[i];
|
||||
let y = row_y_positions[i];
|
||||
let is_being_dragged = drag_layer_ids_content.contains(&row.layer_id());
|
||||
|
||||
// Skip if layer is outside visible area
|
||||
if y + LAYER_HEIGHT < rect.min.y || y > rect.max.y {
|
||||
|
|
@ -1765,6 +1840,11 @@ impl TimelinePane {
|
|||
egui::vec2(rect.width(), LAYER_HEIGHT),
|
||||
);
|
||||
|
||||
// Drop shadow for dragged rows
|
||||
if is_being_dragged {
|
||||
paint_drop_shadow(painter, layer_rect, 8.0, 60);
|
||||
}
|
||||
|
||||
let row_layer_id = row.layer_id();
|
||||
|
||||
// Active vs inactive background colors
|
||||
|
|
@ -2913,18 +2993,6 @@ impl TimelinePane {
|
|||
);
|
||||
}
|
||||
|
||||
// Draw gap slots in content area for layer drag (matching active row style)
|
||||
if let Some(gap) = gap_row_index_content {
|
||||
for di in 0..drag_count_content {
|
||||
let gap_y = rect.min.y + (gap + di) as f32 * LAYER_HEIGHT - self.viewport_scroll_y;
|
||||
let gap_rect = egui::Rect::from_min_size(
|
||||
egui::pos2(rect.min.x, gap_y),
|
||||
egui::vec2(rect.width(), LAYER_HEIGHT),
|
||||
);
|
||||
painter.rect_filled(gap_rect, 0.0, active_color);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up stale video thumbnail textures for clips no longer visible
|
||||
self.video_thumbnail_textures.retain(|&(clip_id, _), _| visible_video_clip_ids.contains(&clip_id));
|
||||
|
||||
|
|
@ -3175,7 +3243,7 @@ impl TimelinePane {
|
|||
if primary_down {
|
||||
if let Some(pos) = pointer_pos {
|
||||
drag.current_mouse_y = pos.y;
|
||||
let relative_y = pos.y - header_rect.min.y + self.viewport_scroll_y;
|
||||
let relative_y = pos.y - drag.grab_offset_y - header_rect.min.y + self.viewport_scroll_y + LAYER_HEIGHT * 0.5;
|
||||
let all_rows = build_timeline_rows(context_layers);
|
||||
let filtered_count = all_rows.iter()
|
||||
.filter(|r| !drag.layer_ids.contains(&r.layer_id()))
|
||||
|
|
|
|||
Loading…
Reference in New Issue