1use super::{
13 events::{InteractionContext, InteractionResult, MouseEventKind},
14 InteractionStyle, KeyEvent, MouseButton, MouseEvent,
15};
16use crate::{camera::Camera, window_level::WindowLevel};
17
18#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
20enum DragState {
21 #[default]
22 None,
23 WindowLevel,
24 Panning,
25 Slicing,
26}
27
28#[derive(Debug)]
36pub struct ImageSliceStyle {
37 drag: DragState,
38 last_pos: (f64, f64),
39 window_level: WindowLevel,
40 slice_delta: f64,
41 pub window_sensitivity: f64,
43 pub level_sensitivity: f64,
45 pub slice_scroll_step: f64,
47}
48
49impl ImageSliceStyle {
50 #[must_use]
52 pub fn new(window_level: WindowLevel) -> Self {
53 Self {
54 drag: DragState::None,
55 last_pos: (0.0, 0.0),
56 window_level,
57 slice_delta: 0.0,
58 window_sensitivity: 4.0,
59 level_sensitivity: 2.0,
60 slice_scroll_step: 1.0,
61 }
62 }
63
64 #[must_use]
66 pub fn window_level(&self) -> WindowLevel {
67 self.window_level
68 }
69
70 pub fn take_slice_delta(&mut self) -> f64 {
74 let d = self.slice_delta;
75 self.slice_delta = 0.0;
76 d
77 }
78}
79
80impl InteractionStyle for ImageSliceStyle {
81 fn on_mouse_event(
82 &mut self,
83 event: &MouseEvent,
84 _ctx: &InteractionContext,
85 camera: &mut Camera,
86 ) -> InteractionResult {
87 match event.kind {
88 MouseEventKind::Press(button) => {
89 self.last_pos = event.position;
90 self.drag = match button {
91 MouseButton::Left => DragState::WindowLevel,
92 MouseButton::Middle => DragState::Panning,
93 MouseButton::Right => DragState::Slicing,
94 };
95 InteractionResult::nothing()
96 }
97
98 MouseEventKind::Release(_) => {
99 self.drag = DragState::None;
100 InteractionResult::nothing()
101 }
102
103 MouseEventKind::Move => {
104 let dx = event.position.0 - self.last_pos.0;
105 let dy = event.position.1 - self.last_pos.1;
106 self.last_pos = event.position;
107
108 if dx == 0.0 && dy == 0.0 {
109 return InteractionResult::nothing();
110 }
111
112 match self.drag {
113 DragState::None => InteractionResult::nothing(),
114
115 DragState::WindowLevel => {
116 self.window_level.width =
117 (self.window_level.width + dx * self.window_sensitivity).max(1.0);
118 self.window_level.center += dy * self.level_sensitivity;
119 InteractionResult::window_level_only()
120 }
121
122 DragState::Panning => {
123 let scale = camera.distance() * 0.001;
124 let right = camera.right();
125 let up = camera.view_up_ortho();
126 camera.pan(-right * dx * scale + up * dy * scale);
127 InteractionResult::camera_only()
128 }
129
130 DragState::Slicing => {
131 self.slice_delta += dy * self.slice_scroll_step * 0.1;
132 InteractionResult::slice_only()
133 }
134 }
135 }
136
137 MouseEventKind::Scroll(delta) => {
138 self.slice_delta += delta * self.slice_scroll_step;
139 InteractionResult::slice_only()
140 }
141 }
142 }
143
144 fn on_key_event(
145 &mut self,
146 _event: &KeyEvent,
147 _ctx: &InteractionContext,
148 _camera: &mut Camera,
149 ) -> InteractionResult {
150 InteractionResult::nothing()
151 }
152}
153
154#[cfg(test)]
157mod tests {
158 use super::*;
159 use crate::interaction::Modifiers;
160 use crate::window_level::presets;
161 use glam::DVec3;
162
163 fn default_camera() -> Camera {
164 Camera::new(DVec3::new(0.0, 0.0, 10.0), DVec3::ZERO, DVec3::Y)
165 }
166
167 fn ctx() -> InteractionContext {
168 InteractionContext {
169 viewport_width: 800.0,
170 viewport_height: 600.0,
171 volume_bounds: None,
172 }
173 }
174
175 fn mouse(pos: (f64, f64), kind: MouseEventKind) -> MouseEvent {
176 MouseEvent {
177 position: pos,
178 kind,
179 modifiers: Modifiers::default(),
180 }
181 }
182
183 #[test]
184 fn left_drag_horizontal_changes_window_width() {
185 let mut style = ImageSliceStyle::new(presets::SOFT_TISSUE);
186 let mut cam = default_camera();
187 let w0 = style.window_level().width;
188
189 style.on_mouse_event(
190 &mouse((0.0, 0.0), MouseEventKind::Press(MouseButton::Left)),
191 &ctx(),
192 &mut cam,
193 );
194 let r = style.on_mouse_event(&mouse((50.0, 0.0), MouseEventKind::Move), &ctx(), &mut cam);
195
196 assert!(r.window_level_changed);
197 assert!(style.window_level().width > w0, "width should increase");
198 }
199
200 #[test]
201 fn left_drag_vertical_changes_center() {
202 let mut style = ImageSliceStyle::new(presets::SOFT_TISSUE);
203 let mut cam = default_camera();
204 let c0 = style.window_level().center;
205
206 style.on_mouse_event(
207 &mouse((0.0, 0.0), MouseEventKind::Press(MouseButton::Left)),
208 &ctx(),
209 &mut cam,
210 );
211 style.on_mouse_event(&mouse((0.0, 30.0), MouseEventKind::Move), &ctx(), &mut cam);
212
213 assert_ne!(style.window_level().center, c0, "center should change");
214 }
215
216 #[test]
217 fn scroll_accumulates_slice_delta() {
218 let mut style = ImageSliceStyle::new(presets::SOFT_TISSUE);
219 let mut cam = default_camera();
220
221 style.on_mouse_event(
222 &mouse((400.0, 300.0), MouseEventKind::Scroll(3.0)),
223 &ctx(),
224 &mut cam,
225 );
226 let d = style.take_slice_delta();
227 assert!(d > 0.0, "scroll up should give positive delta");
228 assert_eq!(style.take_slice_delta(), 0.0, "delta consumed");
229 }
230}