@@ -5,8 +5,9 @@ use crate::{
55 feature_uniform:: { FeatureUniform , TransformAction } ,
66 gpu_state:: GpuResourceAllocator ,
77 mouse_state:: MouseState ,
8- shader:: Shader ,
9- shape:: { compute_radius, Shape , ShapeStack } ,
8+ shader:: { Shader , TextureResource } ,
9+ shape:: { compute_distance, RevisionStack , Shape } ,
10+ shape_uniform:: { CircleData , ShapeUniform , MAX_CIRCLES } ,
1011 } ,
1112} ;
1213use anyhow:: Result ;
@@ -29,9 +30,13 @@ pub struct AppState<'a> {
2930 pub feature_uniform : FeatureUniform ,
3031 pub draw_uniform : DrawUniform ,
3132 pub mouse_state : MouseState ,
32- pub shape_stack : ShapeStack ,
33+ pub shape_stack : RevisionStack ,
3334
3435 pub image_shader : Shader ,
36+ pub shape_shader : Shader ,
37+ pub shape_render_texture : TextureResource ,
38+ pub shape_uniform : ShapeUniform ,
39+ pub circle_storage_buffer : wgpu:: Buffer ,
3540}
3641
3742impl < ' a > AppState < ' a > {
@@ -51,15 +56,42 @@ impl<'a> AppState<'a> {
5156 let draw_uniform_resource =
5257 gpu_allocator. create_uniform_resource ( "draw_uniform" , draw_uniform) ?;
5358
59+ // Create shape render texture
60+ let shape_render_texture =
61+ gpu_allocator. create_render_texture ( "shape_texture" , size. width , size. height ) ;
62+
63+ // Create shape uniform and storage buffer
64+ let shape_uniform = ShapeUniform :: new ( size. width , size. height ) ;
65+ let shape_uniform_resource =
66+ gpu_allocator. create_uniform_resource ( "shape_uniform" , shape_uniform) ?;
67+
68+ // Create empty circle storage buffer
69+ let empty_circles = vec ! [ CircleData :: default ( ) ; MAX_CIRCLES ] ;
70+ let circle_storage_buffer =
71+ gpu_allocator. create_storage_buffer ( "circle_storage" , & empty_circles) ?;
72+
73+ let shape_shader = gpu_allocator. create_shape_shader (
74+ "shape_shader" ,
75+ include_str ! ( "shape_shader.wgsl" ) ,
76+ shape_uniform_resource,
77+ & circle_storage_buffer,
78+ ) ;
79+
80+ // Create a reference to the shape texture for the image shader
81+ let shape_texture_for_image = gpu_allocator. create_texture_resource_from_existing (
82+ "shape_texture_ref" ,
83+ & shape_render_texture. resource ,
84+ ) ;
85+
5486 let image_shader = gpu_allocator. create_shader (
5587 "image_shader" ,
5688 include_str ! ( "image_shader.wgsl" ) ,
57- vec ! [ image_texture_resource] ,
89+ vec ! [ image_texture_resource, shape_texture_for_image ] ,
5890 vec ! [ feature_uniform_resource, draw_uniform_resource] ,
5991 ) ;
6092
6193 let mouse_state = MouseState :: default ( ) ;
62- let shape_stack = ShapeStack :: new ( ) ;
94+ let shape_stack = RevisionStack :: new ( ) ;
6395
6496 Ok ( Self {
6597 gpu_allocator,
@@ -70,6 +102,10 @@ impl<'a> AppState<'a> {
70102 mouse_state,
71103 shape_stack,
72104 image_shader,
105+ shape_shader,
106+ shape_render_texture,
107+ shape_uniform,
108+ circle_storage_buffer,
73109 } )
74110 }
75111
@@ -83,6 +119,8 @@ impl<'a> AppState<'a> {
83119 self . gpu_allocator . configure_surface ( & new_size) ;
84120 self . feature_uniform
85121 . update_window_dimensions ( new_size. width , new_size. height ) ;
122+ self . shape_uniform
123+ . update_dimensions ( new_size. width , new_size. height ) ;
86124 }
87125 }
88126
@@ -94,50 +132,111 @@ impl<'a> AppState<'a> {
94132 WindowEvent :: MouseInput { state, button, .. } => {
95133 if * button == MouseButton :: Left {
96134 let prev_state = self . mouse_state . pressed ( ) ;
97-
98135 self . mouse_state
99136 . set_pressed ( matches ! ( state, ElementState :: Pressed ) ) ;
100137
101- if !draw_uniform. crosshair ( ) {
102- return true ;
103- }
104-
105- match ( prev_state, self . mouse_state . pressed ( ) ) {
106- ( false , true ) => {
107- let ( start_x, start_y) = self . mouse_state . position ( ) ;
138+ if draw_uniform. crosshair ( ) {
139+ // Crosshair mode: Draw new circles
140+ match ( prev_state, self . mouse_state . pressed ( ) ) {
141+ ( false , true ) => {
142+ let ( start_x, start_y) = self . mouse_state . position ( ) ;
143+ self . mouse_state . set_start_drag ( Some ( ( start_x, start_y) ) ) ;
144+ draw_uniform. set_circle_center ( start_x, start_y) ;
145+ }
146+ ( true , false ) => {
147+ let initial_drag_position = self . mouse_state . start_drag ( ) ;
148+ if initial_drag_position. is_none ( ) {
149+ panic ! ( "Logic error occurred. Mouse state once finished pressing doesn't have initial drag position set." ) ;
150+ }
108151
109- dbg ! ( "start drag" , start_x, start_y) ;
110- self . mouse_state . set_start_drag ( Some ( ( start_x, start_y) ) ) ;
111- draw_uniform. set_circle_center ( start_x, start_y) ;
152+ let ( x, y) = initial_drag_position. unwrap ( ) ;
153+ let ( edge_x, edge_y) = self . mouse_state . position ( ) ;
154+ let radius = compute_distance ( ( x, y) , ( edge_x, edge_y) ) ;
155+
156+ // Convert to normalized coordinates (0-1 range)
157+ let normalized_x = x / self . size . width as f32 ;
158+ let normalized_y = y / self . size . height as f32 ;
159+ let normalized_radius =
160+ radius / ( self . size . width . min ( self . size . height ) as f32 ) ;
161+
162+ self . shape_stack . push ( Shape :: Circle {
163+ x : normalized_x,
164+ y : normalized_y,
165+ radius : normalized_radius,
166+ } ) ;
167+
168+ // Clear state
169+ self . mouse_state . set_start_drag ( None ) ;
170+ draw_uniform. set_circle_radius ( 0.0 ) ;
171+ }
172+ _ => { }
112173 }
113- ( true , false ) => {
114- let initial_drag_position = self . mouse_state . start_drag ( ) ;
115-
116- if initial_drag_position. is_none ( ) {
117- panic ! ( "Logic error occured. Mouse state once finished pressing doesn't have initial drag position set." ) ;
174+ } else {
175+ // Selection mode: Select and move circles
176+ match ( prev_state, self . mouse_state . pressed ( ) ) {
177+ ( false , true ) => {
178+ // Mouse press - check if we clicked on a circle
179+ let ( mouse_x, mouse_y) = self . mouse_state . position ( ) ;
180+ let normalized_x = mouse_x / self . size . width as f32 ;
181+ let normalized_y = mouse_y / self . size . height as f32 ;
182+
183+ if let Some ( circle_index) = self . shape_stack . find_shape_at_point (
184+ normalized_x,
185+ normalized_y,
186+ self . size . width ,
187+ self . size . height ,
188+ ) {
189+ // Found a circle - select it and start dragging
190+ self . mouse_state . set_selected_shape ( Some ( circle_index) ) ;
191+ self . mouse_state . set_dragging_shape ( true ) ;
192+
193+ // Calculate offset from circle center to mouse position
194+ if let Some ( Shape :: Circle { x, y, .. } ) =
195+ self . shape_stack . shapes ( ) . get ( circle_index)
196+ {
197+ let offset_x = normalized_x - x;
198+ let offset_y = normalized_y - y;
199+ self . mouse_state . set_drag_offset ( ( offset_x, offset_y) ) ;
200+ }
201+ } else {
202+ // Clicked on empty space - deselect any selected circle
203+ self . mouse_state . set_selected_shape ( None ) ;
204+ }
118205 }
119-
120- let ( x, y) = initial_drag_position. unwrap ( ) ;
121- let ( edge_x, edge_y) = self . mouse_state . position ( ) ;
122- let radius = compute_radius ( ( x, y) , ( edge_x, edge_y) ) ;
123- self . shape_stack . push ( Shape :: Circle { x, y, radius } ) ;
124-
125- // clear state
126- self . mouse_state . set_start_drag ( None ) ;
127- dbg ! ( "stop drag" ) ;
128- draw_uniform. set_circle_radius ( 0.0 ) ;
206+ ( true , false ) => {
207+ // Mouse release - stop dragging
208+ self . mouse_state . set_dragging_shape ( false ) ;
209+ }
210+ _ => { }
129211 }
130- _ => { }
131212 }
132213 }
133214 }
134215 WindowEvent :: CursorMoved { position, .. } => {
135216 let ( x, y) = ( position. x as f32 , position. y as f32 ) ;
136217
137- if let Some ( center) = self . mouse_state . start_drag ( ) {
138- let radius = compute_radius ( center, ( x, y) ) ;
139- dbg ! ( "dragging: radius" , radius) ;
140- self . draw_uniform . set_circle_radius ( radius) ;
218+ if draw_uniform. crosshair ( ) {
219+ // Crosshair mode: Update the preview circle radius
220+ if let Some ( center) = self . mouse_state . start_drag ( ) {
221+ let radius = compute_distance ( center, ( x, y) ) ;
222+ self . draw_uniform . set_circle_radius ( radius) ;
223+ }
224+ } else {
225+ // Selection mode: Handle circle dragging
226+ if self . mouse_state . dragging_shape ( ) {
227+ if let Some ( selected_index) = self . mouse_state . selected_shape ( ) {
228+ let normalized_x = x / self . size . width as f32 ;
229+ let normalized_y = y / self . size . height as f32 ;
230+
231+ // Apply the drag offset to maintain relative position
232+ let ( offset_x, offset_y) = self . mouse_state . drag_offset ( ) ;
233+ let new_x = normalized_x - offset_x;
234+ let new_y = normalized_y - offset_y;
235+
236+ // Move the circle to the new position
237+ self . shape_stack . move_shape ( selected_index, new_x, new_y) ;
238+ }
239+ }
141240 }
142241
143242 self . mouse_state . update_position ( x, y) ;
@@ -202,6 +301,15 @@ impl<'a> AppState<'a> {
202301 ( KeyCode :: KeyY , ElementState :: Pressed ) => {
203302 feature_uniform. apply_transform ( TransformAction :: FlipY ) ;
204303 }
304+ ( KeyCode :: Delete , ElementState :: Pressed )
305+ | ( KeyCode :: Backspace , ElementState :: Pressed ) => {
306+ // Delete the selected circle
307+ if let Some ( selected_index) = self . mouse_state . selected_shape ( ) {
308+ self . shape_stack . remove_shape ( selected_index) ;
309+ self . mouse_state . set_selected_shape ( None ) ;
310+ self . mouse_state . set_dragging_shape ( false ) ;
311+ }
312+ }
205313 _ => return false ,
206314 } ,
207315 _ => return false ,
@@ -210,21 +318,76 @@ impl<'a> AppState<'a> {
210318 true
211319 }
212320
213- pub ( crate ) fn update ( & self ) {
214- // Make sure the uniform resource at index i correctly matches up to the uniform structure.
321+ pub ( crate ) fn update ( & mut self ) {
322+ // Update image shader uniforms
215323 let uniform_resources = & self . image_shader . uniform_resources ;
216-
217324 self . gpu_allocator
218325 . write_uniform_buffer ( & uniform_resources[ 0 ] . resource , self . feature_uniform ) ;
219-
220326 self . gpu_allocator
221327 . write_uniform_buffer ( & uniform_resources[ 1 ] . resource , self . draw_uniform ) ;
328+
329+ // Update shape data
330+ self . update_shape_data ( ) ;
331+ }
332+
333+ fn update_shape_data ( & mut self ) {
334+ let shapes = self . shape_stack . shapes ( ) ;
335+ let num_circles = shapes. len ( ) . min ( MAX_CIRCLES ) ;
336+
337+ self . shape_uniform . set_num_circles ( num_circles as u32 ) ;
338+ self . shape_uniform
339+ . set_selected_circle ( self . mouse_state . selected_shape ( ) ) ;
340+
341+ // Update shape uniform
342+ let shape_uniform_resources = & self . shape_shader . uniform_resources ;
343+ self . gpu_allocator
344+ . write_uniform_buffer ( & shape_uniform_resources[ 0 ] . resource , self . shape_uniform ) ;
345+
346+ // Update circle storage buffer
347+ let mut circle_data = vec ! [ CircleData :: default ( ) ; MAX_CIRCLES ] ;
348+ for ( i, shape) in shapes. iter ( ) . take ( MAX_CIRCLES ) . enumerate ( ) {
349+ circle_data[ i] = CircleData :: from ( shape) ;
350+ }
351+
352+ self . gpu_allocator
353+ . write_storage_buffer ( & self . circle_storage_buffer , & circle_data) ;
222354 }
223355
224356 pub ( crate ) fn render ( & self ) -> Result < ( ) , wgpu:: SurfaceError > {
225357 let ( output, view, mut encoder) = self . gpu_allocator . begin_frame ( ) ?;
226358
227- // Image shader render pass
359+ // First pass: Render shapes to shape texture
360+ {
361+ let mut render_pass = encoder. begin_render_pass ( & wgpu:: RenderPassDescriptor {
362+ label : Some ( "shape render pass" ) ,
363+ color_attachments : & [ Some ( wgpu:: RenderPassColorAttachment {
364+ view : & self . shape_render_texture . resource . view ,
365+ resolve_target : None ,
366+ ops : wgpu:: Operations {
367+ load : wgpu:: LoadOp :: Clear ( wgpu:: Color :: TRANSPARENT ) ,
368+ store : wgpu:: StoreOp :: Store ,
369+ } ,
370+ } ) ] ,
371+ depth_stencil_attachment : None ,
372+ occlusion_query_set : None ,
373+ timestamp_writes : None ,
374+ } ) ;
375+
376+ render_pass. set_pipeline ( & self . shape_shader . render_pipeline ) ;
377+
378+ // Set bind group for shape shader (uniform + storage buffer)
379+ render_pass. set_bind_group ( 0 , & self . shape_shader . uniform_resources [ 0 ] . bind_group , & [ ] ) ;
380+
381+ render_pass. set_vertex_buffer ( 0 , self . gpu_allocator . vertex_buffer . slice ( ..) ) ;
382+ render_pass. set_index_buffer (
383+ self . gpu_allocator . index_buffer . slice ( ..) ,
384+ wgpu:: IndexFormat :: Uint16 ,
385+ ) ;
386+
387+ render_pass. draw_indexed ( 0 ..self . gpu_allocator . num_indices ( ) , 0 , 0 ..1 ) ;
388+ }
389+
390+ // Second pass: Render final image with shapes composited
228391 {
229392 let mut render_pass = encoder. begin_render_pass ( & wgpu:: RenderPassDescriptor {
230393 label : Some ( "content render pass" ) ,
0 commit comments