@@ -3,40 +3,28 @@ use processing::prelude::*;
33use pyo3:: exceptions:: PyRuntimeError ;
44use pyo3:: prelude:: * ;
55use pyo3:: types:: PyAny ;
6- use std:: cell:: RefCell ;
76
87use crate :: glfw:: GlfwContext ;
98
10- // The global graphics context
11- // we use thread_local! to ensure that the context is specific to the current thread, particularly
12- // becausae glfw requires that all window operations happen on the main thread.
13- // TODO: we'll want to think about how to enable multi-window / multi-threaded usage in the future
14- thread_local ! {
15- static GRAPHICS_CTX : RefCell <Option <Py <Graphics >>> = const { RefCell :: new( None ) } ;
16- }
17-
189#[ pyclass( unsendable) ]
1910pub struct Graphics {
20- // this makes our object !Send, hence unsendable above
2111 glfw_ctx : GlfwContext ,
2212 surface : Entity ,
2313}
2414
2515#[ pymethods]
2616impl Graphics {
27- // TODO: in theory users can create multiple Graphics objects, which they manually manage themselves.
28- // right now we just support the single global window via the module-level functions.
2917 #[ new]
30- fn new ( width : u32 , height : u32 ) -> PyResult < Self > {
18+ pub fn new ( width : u32 , height : u32 ) -> PyResult < Self > {
3119 let glfw_ctx = GlfwContext :: new ( width, height)
32- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "Couold not create window {e}" ) ) ) ?;
20+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ?;
3321
34- init ( ) . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "Failed to initialize processing {e}" ) ) ) ?;
22+ init ( ) . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ?;
3523
3624 let window_handle = glfw_ctx. get_window ( ) ;
3725 let display_handle = glfw_ctx. get_display ( ) ;
3826 let surface = surface_create ( window_handle, display_handle, width, height, 1.0 )
39- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "Could not create surface {e}" ) ) ) ?;
27+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ?;
4028
4129 Ok ( Self { glfw_ctx, surface } )
4230 }
@@ -45,73 +33,64 @@ impl Graphics {
4533 let ( r, g, b, a) = parse_color ( & args) ?;
4634 let color = bevy:: color:: Color :: srgba ( r, g, b, a) ;
4735 record_command ( self . surface , DrawCommand :: BackgroundColor ( color) )
48- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "background failed {e}" ) ) )
36+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) )
4937 }
5038
5139 pub fn fill ( & self , args : Vec < f32 > ) -> PyResult < ( ) > {
5240 let ( r, g, b, a) = parse_color ( & args) ?;
5341 let color = bevy:: color:: Color :: srgba ( r, g, b, a) ;
5442 record_command ( self . surface , DrawCommand :: Fill ( color) )
55- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "fill failed {e}" ) ) )
43+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) )
5644 }
5745
5846 pub fn no_fill ( & self ) -> PyResult < ( ) > {
5947 record_command ( self . surface , DrawCommand :: NoFill )
60- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "no_fill failed {e}" ) ) )
48+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) )
6149 }
6250
6351 pub fn stroke ( & self , args : Vec < f32 > ) -> PyResult < ( ) > {
6452 let ( r, g, b, a) = parse_color ( & args) ?;
6553 let color = bevy:: color:: Color :: srgba ( r, g, b, a) ;
6654 record_command ( self . surface , DrawCommand :: StrokeColor ( color) )
67- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "stroke failed {e}" ) ) )
55+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) )
6856 }
6957
7058 pub fn no_stroke ( & self ) -> PyResult < ( ) > {
7159 record_command ( self . surface , DrawCommand :: NoStroke )
72- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "no_stroke failed {e}" ) ) )
60+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) )
7361 }
7462
7563 pub fn stroke_weight ( & self , weight : f32 ) -> PyResult < ( ) > {
7664 record_command ( self . surface , DrawCommand :: StrokeWeight ( weight) )
77- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "stroke_weight failed {e}" ) ) )
65+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) )
7866 }
7967
8068 pub fn rect ( & self , x : f32 , y : f32 , w : f32 , h : f32 , tl : f32 , tr : f32 , br : f32 , bl : f32 ) -> PyResult < ( ) > {
8169 record_command (
8270 self . surface ,
83- DrawCommand :: Rect {
84- x,
85- y,
86- w,
87- h,
88- radii : [ tl, tr, br, bl] ,
89- } ,
71+ DrawCommand :: Rect { x, y, w, h, radii : [ tl, tr, br, bl] } ,
9072 )
91- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "rect failed {e}" ) ) )
73+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) )
9274 }
9375
9476 pub fn run ( & mut self , draw_fn : Option < Py < PyAny > > ) -> PyResult < ( ) > {
9577 loop {
96- let running = self . glfw_ctx . poll_events ( ) ;
97- if !running {
78+ if !self . glfw_ctx . poll_events ( ) {
9879 break ;
9980 }
10081
10182 begin_draw ( self . surface )
102- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "begin_draw failed {e}" ) ) ) ?;
83+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ?;
10384
10485 if let Some ( ref draw) = draw_fn {
10586 Python :: attach ( |py| {
106- draw. call0 ( py)
107- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "draw failed {e}" ) ) )
87+ draw. call0 ( py) . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) )
10888 } ) ?;
10989 }
11090
11191 end_draw ( self . surface )
112- . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "end_draw failed {e}" ) ) ) ?;
92+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) ) ?;
11393 }
114-
11594 Ok ( ( ) )
11695 }
11796}
@@ -120,68 +99,34 @@ impl Graphics {
12099// about how to expose different color spaces in an idiomatic pythonic way
121100fn parse_color ( args : & [ f32 ] ) -> PyResult < ( f32 , f32 , f32 , f32 ) > {
122101 match args. len ( ) {
123- 4 => Ok ( (
124- args[ 0 ] / 255.0 ,
125- args[ 1 ] / 255.0 ,
126- args[ 2 ] / 255.0 ,
127- args[ 3 ] / 255.0 ,
128- ) ) ,
129- _ => Err ( PyRuntimeError :: new_err (
130- "Color requires 4 arguments" ,
131- ) ) ,
102+ 1 => {
103+ let v = args[ 0 ] / 255.0 ;
104+ Ok ( ( v, v, v, 1.0 ) )
105+ }
106+ 2 => {
107+ let v = args[ 0 ] / 255.0 ;
108+ Ok ( ( v, v, v, args[ 1 ] / 255.0 ) )
109+ }
110+ 3 => Ok ( ( args[ 0 ] / 255.0 , args[ 1 ] / 255.0 , args[ 2 ] / 255.0 , 1.0 ) ) ,
111+ 4 => Ok ( ( args[ 0 ] / 255.0 , args[ 1 ] / 255.0 , args[ 2 ] / 255.0 , args[ 3 ] / 255.0 ) ) ,
112+ _ => Err ( PyRuntimeError :: new_err ( "color requires 1-4 arguments" ) ) ,
132113 }
133114}
134115
135- /// Run inside the current graphics context
136- pub fn with_graphics < F , T > ( f : F ) -> PyResult < T >
137- where
138- F : FnOnce ( PyRef < ' _ , Graphics > ) -> PyResult < T > ,
139- {
140- GRAPHICS_CTX . with ( |cell| {
141- let opt = cell. borrow ( ) ;
142- match opt. as_ref ( ) {
143- Some ( py_graphics) => Python :: attach ( |py| {
144- let graphics = py_graphics. bind ( py) . borrow ( ) ;
145- f ( graphics)
146- } ) ,
147- None => Err ( PyRuntimeError :: new_err (
148- "No graphics context" ,
149- ) ) ,
150- }
151- } )
116+ pub fn get_graphics < ' py > ( module : & Bound < ' py , PyModule > ) -> PyResult < PyRef < ' py , Graphics > > {
117+ module
118+ . getattr ( "_graphics" ) ?
119+ . cast_into :: < Graphics > ( )
120+ . map_err ( |_| PyRuntimeError :: new_err ( "no graphics context" ) ) ?
121+ . try_borrow ( )
122+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) )
152123}
153124
154- /// Run inside the current graphics context with mutable access
155- pub fn with_graphics_mut < F , T > ( f : F ) -> PyResult < T >
156- where
157- F : FnOnce ( PyRefMut < ' _ , Graphics > ) -> PyResult < T > ,
158- {
159- GRAPHICS_CTX . with ( |cell| {
160- let opt = cell. borrow ( ) ;
161- match opt. as_ref ( ) {
162- Some ( py_graphics) => Python :: attach ( |py| {
163- let graphics = py_graphics. bind ( py) . borrow_mut ( ) ;
164- f ( graphics)
165- } ) ,
166- None => Err ( PyRuntimeError :: new_err (
167- "No graphics context" ,
168- ) ) ,
169- }
170- } )
125+ pub fn get_graphics_mut < ' py > ( module : & Bound < ' py , PyModule > ) -> PyResult < PyRefMut < ' py , Graphics > > {
126+ module
127+ . getattr ( "_graphics" ) ?
128+ . cast_into :: < Graphics > ( )
129+ . map_err ( |_| PyRuntimeError :: new_err ( "no graphics context" ) ) ?
130+ . try_borrow_mut ( )
131+ . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "{e}" ) ) )
171132}
172-
173- /// Create the module level graphics context
174- pub fn create_context ( width : u32 , height : u32 ) -> PyResult < ( ) > {
175- let already_exists = GRAPHICS_CTX . with ( |cell| cell. borrow ( ) . is_some ( ) ) ;
176- if already_exists {
177- return Err ( PyRuntimeError :: new_err ( "A context already exists" ) ) ;
178- }
179-
180- Python :: attach ( |py| {
181- let graphics = Py :: new ( py, Graphics :: new ( width, height) ?) ?;
182- GRAPHICS_CTX . with ( |cell| {
183- * cell. borrow_mut ( ) = Some ( graphics) ;
184- } ) ;
185- Ok ( ( ) )
186- } )
187- }
0 commit comments