1use blockworx_doc::block_model::Asset;
2use blockworx_geom::{Pos2, Rect, pos2, vec2};
3
4use blockworx_paint::{AnimKey, Animator, Renderer};
5
6use crate::{
7 grid::{GRID_SIZE, LOCK_HINT_SIZE, PORT_RADIUS, round_to_grid},
8 state::ResizeMode,
9 theme::{Role, Style},
10 tools::new_pin::{NEW_PIN_ANIM_TIME, NEW_PIN_GROW_RANGE, NEW_PIN_INACTIVE_SCALE},
11};
12
13const LOCK_HINT_SVG: &str = include_str!("../../icons/icon-lock.svg");
16
17pub(crate) const RESIZE_INACTIVE_SCALE: f32 = NEW_PIN_INACTIVE_SCALE * 1.3;
21
22#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
25enum Corner {
26 LeftTop,
27 RightTop,
28 LeftBottom,
29 RightBottom,
30}
31
32impl Corner {
33 const ALL: [Corner; 4] = [
34 Corner::LeftTop,
35 Corner::RightTop,
36 Corner::LeftBottom,
37 Corner::RightBottom,
38 ];
39
40 fn pos(self, bbox: Rect) -> Pos2 {
41 match self {
42 Corner::LeftTop => bbox.left_top(),
43 Corner::RightTop => bbox.right_top(),
44 Corner::LeftBottom => bbox.left_bottom(),
45 Corner::RightBottom => bbox.right_bottom(),
46 }
47 }
48
49 fn from_mode(mode: ResizeMode) -> Corner {
50 match mode {
51 ResizeMode::LeftTop => Corner::LeftTop,
52 ResizeMode::RightTop => Corner::RightTop,
53 ResizeMode::LeftBottom => Corner::LeftBottom,
54 ResizeMode::RightBottom => Corner::RightBottom,
55 }
56 }
57}
58
59fn anim_key(bbox: Rect, corner: Corner) -> AnimKey {
64 let pos = corner.pos(bbox);
65 AnimKey::of((
66 "resize_corner",
67 corner,
68 round_to_grid(pos.x) as i32,
69 round_to_grid(pos.y) as i32,
70 ))
71}
72
73fn draw_armed_handle(pos: Pos2, painter: &mut Style<'_, impl Renderer>) {
76 painter.circle(
77 pos,
78 PORT_RADIUS,
79 Role::ResizeCornerActiveFill,
80 (1.0, Role::ResizeCornerActiveStroke),
81 );
82}
83
84fn grown(bbox: Rect, corner: Corner, pointer: Option<Pos2>, anim: &dyn Animator) -> f32 {
88 let goal = if pointer.is_some_and(|p| corner.pos(bbox).distance(p) < NEW_PIN_GROW_RANGE.get()) {
89 1.0
90 } else {
91 0.0
92 };
93 anim.animate(anim_key(bbox, corner), goal, NEW_PIN_ANIM_TIME)
94}
95
96fn draw_corner_handle(pos: Pos2, t: f32, painter: &mut Style<'_, impl Renderer>) {
100 if t < 1.0 {
101 painter.with_opacity(1.0 - t, |p| {
102 p.circle_filled(
103 pos,
104 PORT_RADIUS * RESIZE_INACTIVE_SCALE,
105 Role::ResizeCornerFill,
106 );
107 });
108 }
109 if t > 0.0 {
110 let radius = PORT_RADIUS * (RESIZE_INACTIVE_SCALE + (1.0 - RESIZE_INACTIVE_SCALE) * t);
111 painter.with_opacity(t, |p| {
112 p.circle(
113 pos,
114 radius,
115 Role::ResizeCornerFill,
116 (0.5, Role::ResizeCornerStroke),
117 );
118 });
119 }
120}
121
122pub fn draw_selection_frame(
132 bbox: Rect,
133 mode: Option<ResizeMode>,
134 painter: &mut Style<'_, impl Renderer>,
135) {
136 let active = mode.map(Corner::from_mode);
137 let growths = painter.animator().map(|anim| {
138 let pointer = anim.pointer_world();
139 Corner::ALL
142 .map(|corner| (active != Some(corner)).then(|| grown(bbox, corner, pointer, anim)))
143 });
144 for (corner, growth) in Corner::ALL.into_iter().zip(growths.unwrap_or([None; 4])) {
145 if active == Some(corner) {
146 continue;
147 }
148 match growth {
149 Some(t) => draw_corner_handle(corner.pos(bbox), t, painter),
150 None => painter.circle(
151 corner.pos(bbox),
152 PORT_RADIUS,
153 Role::ResizeCornerFill,
154 (0.5, Role::ResizeCornerStroke),
155 ),
156 }
157 }
158 if let Some(corner) = active {
159 draw_armed_handle(corner.pos(bbox), painter);
160 }
161}
162
163pub fn draw_lock_hint(bbox: Rect, painter: &mut Style<'_, impl Renderer>) {
169 let s = LOCK_HINT_SIZE;
170 let m = PORT_RADIUS.get() + GRID_SIZE * 0.2;
171 if bbox.width() < s + m || bbox.height() < s + m {
172 return;
173 }
174
175 let icon = Rect::from_min_size(
176 pos2(bbox.right_top().x - m - s, bbox.right_top().y + m),
177 vec2(s, s),
178 );
179 let color = painter.theme().resolve(Role::LockedHintIcon);
180 let tinted = LOCK_HINT_SVG.replace(
181 "stroke=\"#ffffff\"",
182 &format!(
183 "stroke=\"#{:02x}{:02x}{:02x}\" stroke-opacity=\"{:.3}\"",
184 color.r(),
185 color.g(),
186 color.b(),
187 color.a() as f32 / 255.0
188 ),
189 );
190 painter.draw_image(icon, &Asset::Svg(tinted.into_bytes().into()));
191}