fix(panels): match hidden layer state color

This commit is contained in:
Fini 2026-05-30 15:45:13 +08:00
parent cbf0491c74
commit da198f6432
2 changed files with 84 additions and 12 deletions

View file

@ -632,8 +632,6 @@ impl Widget for LayerPanel {
}
let indent = ROW_PAD_X + f32::from(item.depth) * 12.0;
// Hidden rows dim everything by 50 % alpha — TS parity
// with `opacity-50` on hidden layer rows.
let dim = |c: Color, factor: f32| -> Color {
Color {
r: c.r,
@ -672,16 +670,12 @@ impl Widget for LayerPanel {
icon_color,
1.4,
);
// Name label — dims to muted when hidden so the user
// can tell at a glance which rows are invisible.
let label_color = if item.selected {
dim(self.theme.primary, dim_factor)
} else {
dim(self.theme.card_foreground, dim_factor)
};
let label_x = icon_x + 20.0;
// Reserve space for trailing eye + lock so the label
// doesn't overlap them when the row is hovered/selected.
let label_max_x = row.origin.x + row.size.x - 8.0 - 14.0 - 22.0 - 4.0;
let available_w = (label_max_x - label_x).max(0.0);
if item.renaming {
@ -722,13 +716,14 @@ impl Widget for LayerPanel {
} else {
dim(self.theme.muted_foreground, dim_factor)
};
let lock_locked = Color {
r: 0.92,
g: 0.49,
b: 0.20,
a: 1.0,
let state_color = |r, g, b| Color { r, g, b, a: 1.0 };
let eye_hidden = state_color(0.98039216, 0.8, 0.08235294);
let lock_locked = state_color(0.92, 0.49, 0.20);
let eye_color = if item.hidden {
eye_hidden
} else {
trailing_default
};
let eye_color = trailing_default;
let lock_color = if item.locked {
lock_locked
} else {

View file

@ -128,6 +128,11 @@ fn first_layer_trailing_points(panel: &LayerPanel, rect: Rect) -> (Point2D, Poin
)
}
fn first_layer_eye_top_left(panel: &LayerPanel, rect: Rect) -> Point2D {
let (eye_center, _) = first_layer_trailing_points(panel, rect);
Point2D::new(eye_center.x - 6.0, eye_center.y - 5.0)
}
#[test]
fn selected_visible_unlocked_layer_does_not_expose_trailing_actions_without_hover() {
let state = EditorState::starter();
@ -173,6 +178,78 @@ fn hovered_layer_exposes_trailing_actions() {
);
}
#[derive(Default)]
struct LayerPaintBackend {
strokes: Vec<(Point2D, f32, crate::Color)>,
}
impl crate::RenderBackend for LayerPaintBackend {
fn begin_frame(&mut self) {}
fn end_frame(&mut self) {}
fn fill_rect(&mut self, _: Rect, _: crate::Color) {}
fn stroke_rect(&mut self, _: Rect, _: crate::Color, _: f32) {}
fn draw_text(&mut self, _: &crate::TextLayout, _: Point2D) {}
fn clip_rect(&mut self, _: Rect) {}
fn save(&mut self) {}
fn restore(&mut self) {}
fn translate(&mut self, _: Point2D) {}
fn stroke_line(&mut self, _: Point2D, _: Point2D, _: crate::Color, _: f32) {}
fn fill_round_rect(&mut self, _: Rect, _: f32, _: crate::Color) {}
fn stroke_round_rect(&mut self, _: Rect, _: f32, _: crate::Color, _: f32) {}
fn stroke_svg_path(
&mut self,
_: &str,
top_left: Point2D,
size: f32,
color: crate::Color,
_: f32,
) {
self.strokes.push((top_left, size, color));
}
fn resize(&mut self, _: u32, _: u32) {}
fn dpi_scale(&self) -> f32 {
1.0
}
}
fn approx_point(a: Point2D, b: Point2D) -> bool {
(a.x - b.x).abs() < 1e-4 && (a.y - b.y).abs() < 1e-4
}
fn is_yellow_400(color: crate::Color) -> bool {
(color.r - 250.0 / 255.0).abs() < 1e-4
&& (color.g - 204.0 / 255.0).abs() < 1e-4
&& (color.b - 21.0 / 255.0).abs() < 1e-4
&& (color.a - 1.0).abs() < 1e-4
}
#[test]
fn hidden_layer_eye_icon_uses_ts_yellow_state_color() {
let mut state = EditorState::starter();
state.toggle_node_hidden(&NodeId::new("n10"));
let panel = LayerPanel::from_editor(&state);
let rect = Rect {
origin: Point2D::new(0.0, 0.0),
size: Point2D::new(LAYER_PANEL_WIDTH, panel.intrinsic_height()),
};
let eye_top_left = first_layer_eye_top_left(&panel, rect);
let mut backend = LayerPaintBackend::default();
let mut cx = super::PaintCx {
backend: &mut backend,
};
panel.paint(&mut cx, rect);
assert!(backend
.strokes
.iter()
.any(
|(top_left, size, color)| approx_point(*top_left, eye_top_left)
&& (*size - 12.0).abs() < 1e-4
&& is_yellow_400(*color)
));
}
#[test]
fn hit_test_resolves_add_page_plus_icon() {
let state = EditorState::sample();