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,
|
/// 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).
|
/// enforcing the sibling constraint (all selected layers must share the same parent).
|
||||||
fn shift_toggle_layer(
|
fn shift_toggle_layer(
|
||||||
|
|
@ -1604,9 +1641,8 @@ impl TimelinePane {
|
||||||
egui::vec2(LAYER_HEADER_WIDTH, LAYER_HEIGHT),
|
egui::vec2(LAYER_HEADER_WIDTH, LAYER_HEIGHT),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Drop shadow (offset down-right, semi-transparent black)
|
// Gradient drop shadow
|
||||||
let shadow_rect = float_rect.translate(egui::vec2(3.0, 4.0));
|
paint_drop_shadow(ui.painter(), float_rect, 8.0, 60);
|
||||||
ui.painter().rect_filled(shadow_rect, 2.0, egui::Color32::from_black_alpha(80));
|
|
||||||
|
|
||||||
// Background (active/selected color)
|
// Background (active/selected color)
|
||||||
ui.painter().rect_filled(float_rect, 0.0, active_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)
|
// Build virtual row list (accounts for group expansion)
|
||||||
let all_rows = build_timeline_rows(context_layers);
|
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()
|
let drag_layer_ids_content: Vec<uuid::Uuid> = self.layer_drag.as_ref()
|
||||||
.map(|d| d.layer_ids.clone()).unwrap_or_default();
|
.map(|d| d.layer_ids.clone()).unwrap_or_default();
|
||||||
let drag_count_content = drag_layer_ids_content.len();
|
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 gap_row_index_content = self.layer_drag.as_ref().map(|d| d.gap_row_index);
|
||||||
|
|
||||||
let rows: Vec<&TimelineRow> = all_rows.iter()
|
// Pre-compute Y position for each row.
|
||||||
.filter(|r| !drag_layer_ids_content.contains(&r.layer_id()))
|
// Dragged rows follow the mouse continuously (matching the floating header);
|
||||||
.collect();
|
// 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
|
let row_y_positions: Vec<f32> = {
|
||||||
for (filtered_i, row) in rows.iter().enumerate() {
|
let mut positions = Vec::with_capacity(all_rows.len());
|
||||||
let visual_i = match gap_row_index_content {
|
let mut filtered_i = 0usize;
|
||||||
Some(gap) if filtered_i >= gap => filtered_i + drag_count_content,
|
let mut drag_offset = 0usize;
|
||||||
_ => filtered_i,
|
for row in all_rows.iter() {
|
||||||
};
|
if drag_layer_ids_content.contains(&row.layer_id()) {
|
||||||
let y = rect.min.y + visual_i as f32 * LAYER_HEIGHT - self.viewport_scroll_y;
|
// 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
|
// Skip if layer is outside visible area
|
||||||
if y + LAYER_HEIGHT < rect.min.y || y > rect.max.y {
|
if y + LAYER_HEIGHT < rect.min.y || y > rect.max.y {
|
||||||
|
|
@ -1765,6 +1840,11 @@ impl TimelinePane {
|
||||||
egui::vec2(rect.width(), LAYER_HEIGHT),
|
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();
|
let row_layer_id = row.layer_id();
|
||||||
|
|
||||||
// Active vs inactive background colors
|
// 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
|
// 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));
|
self.video_thumbnail_textures.retain(|&(clip_id, _), _| visible_video_clip_ids.contains(&clip_id));
|
||||||
|
|
||||||
|
|
@ -3175,7 +3243,7 @@ impl TimelinePane {
|
||||||
if primary_down {
|
if primary_down {
|
||||||
if let Some(pos) = pointer_pos {
|
if let Some(pos) = pointer_pos {
|
||||||
drag.current_mouse_y = pos.y;
|
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 all_rows = build_timeline_rows(context_layers);
|
||||||
let filtered_count = all_rows.iter()
|
let filtered_count = all_rows.iter()
|
||||||
.filter(|r| !drag.layer_ids.contains(&r.layer_id()))
|
.filter(|r| !drag.layer_ids.contains(&r.layer_id()))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue