simple_pixels/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2#![warn(missing_docs)]
3
4pub use miniquad;
5pub use rgb;
6pub use simple_blit;
7
8use miniquad::{
9    conf::Conf, window, Backend, Bindings, BufferLayout, BufferSource, BufferType, BufferUsage,
10    CursorIcon, EventHandler, FilterMode, KeyCode, KeyMods, MipmapFilterMode, MouseButton,
11    PassAction, Pipeline, PipelineParams, RenderingBackend, ShaderMeta, ShaderSource,
12    TextureFormat, TextureId, TextureKind, TextureParams, TextureWrap, UniformBlockLayout,
13    VertexAttribute, VertexFormat,
14};
15use rgb::{ComponentBytes, RGBA8};
16use rustc_hash::FxHashMap;
17use simple_blit::{GenericSurface, Surface};
18use std::{
19    future,
20    sync::{mpsc, Arc, Mutex},
21    task::Poll,
22    time::Duration,
23};
24
25#[repr(C)]
26struct Vec2 {
27    x: f32,
28    y: f32,
29}
30
31impl Vec2 {
32    #[inline]
33    pub const fn new(x: f32, y: f32) -> Self {
34        Self { x, y }
35    }
36}
37
38#[repr(C)]
39struct Vertex {
40    pos: Vec2,
41    uv: Vec2,
42}
43
44const SHADER_VERT: &str = r#"#version 100
45attribute vec2 pos;
46attribute vec2 uv;
47
48varying lowp vec2 texcoord;
49
50void main() {
51    gl_Position = vec4(pos.x, pos.y, 0.0, 1.0);
52    texcoord = uv;
53}"#;
54
55const SHADER_FRAG: &str = r#"#version 100
56varying lowp vec2 texcoord;
57
58uniform sampler2D tex;
59
60void main() {
61    gl_FragColor = texture2D(tex, texcoord);
62}"#;
63
64const SHADER_METAL: &str = r#"
65#include <metal_stdlib>
66
67using namespace metal;
68
69struct Vertex {
70    float2 pos   [[attribute(0)]];
71    float2 uv    [[attribute(1)]];
72};
73
74struct FragData {
75    float4 position [[position]];
76    float2 uv       [[user(locn0)]];
77};
78
79vertex FragData vertexShader(
80    Vertex v [[stage_in]]
81) {
82    FragData out;
83
84    out.position = float4(v.pos.x, v.pos.y, 0.0, 1.0);
85    out.uv = v.uv;
86
87    return out;
88}
89
90fragment float4 fragmentShader(
91    FragData in             [[stage_in]],
92    texture2d<float> tex    [[texture(0)]],
93    sampler texSmplr        [[sampler(0)]]
94) {
95    return tex.sample(texSmplr, in.uv);
96}
97"#;
98
99/// Input state of a mouse/keyboard button
100#[derive(Clone, Copy, Debug, PartialEq, Eq)]
101pub enum InputState {
102    /// The button has just been pressed.
103    Pressed,
104    /// The button is being held down.
105    Down,
106    /// The button has just been released.
107    Released,
108}
109
110/// An object that holds the app's global state.
111pub struct Context {
112    backend: Box<dyn RenderingBackend>,
113
114    pipeline: Pipeline,
115    bindings: Bindings,
116
117    instant: f64,
118    delta_time: f64,
119
120    clear_color: RGBA8,
121    framebuffer: Vec<RGBA8>,
122    buf_width: u32,
123    buf_height: u32,
124
125    keys: FxHashMap<KeyCode, InputState>,
126    key_mods: KeyMods,
127    mouse_pos: (f32, f32),
128    mouse_wheel: (f32, f32),
129    mouse_buttons: FxHashMap<MouseButton, InputState>,
130}
131
132impl Context {
133    #[inline]
134    fn texture_params(width: u32, height: u32) -> TextureParams {
135        TextureParams {
136            kind: TextureKind::Texture2D,
137            format: TextureFormat::RGBA8,
138            wrap: TextureWrap::Clamp,
139            min_filter: FilterMode::Nearest,
140            mag_filter: FilterMode::Nearest,
141            mipmap_filter: MipmapFilterMode::None,
142            width,
143            height,
144            ..Default::default()
145        }
146    }
147
148    fn new() -> Self {
149        let mut backend = window::new_rendering_backend();
150
151        let (win_width, win_height) = window::screen_size();
152        let (win_width, win_height) = (win_width as u32, win_height as u32);
153
154        #[rustfmt::skip]
155        let verices: [Vertex; 4] = [
156            Vertex { pos: Vec2::new(-1., -1.), uv: Vec2::new(0., 1.) },
157            Vertex { pos: Vec2::new( 1., -1.), uv: Vec2::new(1., 1.) },
158            Vertex { pos: Vec2::new( 1.,  1.), uv: Vec2::new(1., 0.) },
159            Vertex { pos: Vec2::new(-1.,  1.), uv: Vec2::new(0., 0.) },
160        ];
161        let vertex_buffer = backend.new_buffer(
162            BufferType::VertexBuffer,
163            BufferUsage::Immutable,
164            BufferSource::slice(&verices),
165        );
166
167        let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
168        let index_buffer = backend.new_buffer(
169            BufferType::IndexBuffer,
170            BufferUsage::Immutable,
171            BufferSource::slice(&indices),
172        );
173
174        let texture = backend.new_render_texture(Self::texture_params(win_width, win_height));
175
176        let bindings = Bindings {
177            vertex_buffers: vec![vertex_buffer],
178            index_buffer,
179            images: vec![texture],
180        };
181
182        let shader_meta = ShaderMeta {
183            images: vec!["tex".to_string()],
184            uniforms: UniformBlockLayout { uniforms: vec![] },
185        };
186
187        let shader = backend
188            .new_shader(
189                match backend.info().backend {
190                    Backend::OpenGl => ShaderSource::Glsl {
191                        vertex: SHADER_VERT,
192                        fragment: SHADER_FRAG,
193                    },
194                    Backend::Metal => ShaderSource::Msl {
195                        program: SHADER_METAL,
196                    },
197                },
198                shader_meta,
199            )
200            .unwrap_or_else(|err| panic!("{err}"));
201
202        let pipeline = backend.new_pipeline(
203            &[BufferLayout::default()],
204            &[
205                VertexAttribute::new("pos", VertexFormat::Float2),
206                VertexAttribute::new("uv", VertexFormat::Float2),
207            ],
208            shader,
209            PipelineParams::default(),
210        );
211
212        Self {
213            backend,
214
215            pipeline,
216            bindings,
217
218            instant: miniquad::date::now(),
219            delta_time: 0.,
220
221            clear_color: RGBA8::new(0, 0, 0, 255),
222            framebuffer: vec![RGBA8::new(0, 0, 0, 255); (win_width * win_height) as usize],
223            buf_width: win_width,
224            buf_height: win_height,
225
226            keys: FxHashMap::default(),
227            key_mods: KeyMods {
228                shift: false,
229                ctrl: false,
230                alt: false,
231                logo: false,
232            },
233            mouse_pos: (0., 0.),
234            mouse_wheel: (0., 0.),
235            mouse_buttons: FxHashMap::default(),
236        }
237    }
238
239    #[inline]
240    fn texture(&self) -> TextureId {
241        self.bindings.images[0]
242    }
243
244    #[inline]
245    fn set_texture(&mut self, tex: TextureId) {
246        self.bindings.images[0] = tex;
247    }
248
249    /// Load file from the filesystem (desktop) or do an HTTP request (web).
250    ///
251    /// `path` is a filesystem path on PC and an URL on web.
252    pub fn load_file<F>(&self, path: impl AsRef<str>, on_loaded: F)
253    where
254        F: Fn(Result<Vec<u8>, miniquad::fs::Error>) + 'static,
255    {
256        miniquad::fs::load_file(path.as_ref(), on_loaded);
257    }
258
259    /// Load file from the filesystem (desktop) or do an HTTP request (web).
260    ///
261    /// `path` is a filesystem path on PC and an URL on web.
262    pub async fn load_file_async(
263        &self,
264        path: impl AsRef<str>,
265    ) -> Result<Vec<u8>, miniquad::fs::Error> {
266        let contents = Arc::new(Mutex::new(None));
267
268        {
269            let contents = contents.clone();
270
271            miniquad::fs::load_file(path.as_ref(), move |result| {
272                *contents.lock().unwrap() = Some(result);
273            });
274        }
275
276        future::poll_fn(move |_ctx| {
277            let mut result = contents.lock().unwrap();
278
279            if let Some(result) = result.take() {
280                Poll::Ready(result)
281            } else {
282                Poll::Pending
283            }
284        })
285        .await
286    }
287
288    /// Load file from the filesystem (desktop) or do an HTTP request (web).
289    ///
290    /// `path` is a filesystem path on PC and an URL on web.
291    /// The result is sent to the `Receiver`.
292    #[inline]
293    pub fn load_file_channel(
294        &self,
295        path: impl AsRef<str>,
296    ) -> mpsc::Receiver<Result<Vec<u8>, miniquad::fs::Error>> {
297        let (sender, receiver) = mpsc::sync_channel(1);
298
299        miniquad::fs::load_file(path.as_ref(), move |result| {
300            let _ = sender.try_send(result);
301        });
302
303        receiver
304    }
305
306    /// Display width (in screen coordinates).
307    ///
308    /// Accounts for dpi scale.
309    #[inline]
310    pub fn display_width(&self) -> f32 {
311        window::screen_size().0
312    }
313
314    /// Display height (in screen coordinates).
315    ///
316    /// Accounts for dpi scale.
317    #[inline]
318    pub fn display_height(&self) -> f32 {
319        window::screen_size().1
320    }
321
322    /// Framebuffer width (in pixels).
323    #[inline]
324    pub fn buffer_width(&self) -> u32 {
325        self.buf_width
326    }
327
328    /// Framebuffer height (in pixels).
329    #[inline]
330    pub fn buffer_height(&self) -> u32 {
331        self.buf_height
332    }
333
334    /// The dpi scaling factor (screen coords to framebuffer pixels).
335    /// See <https://docs.rs/miniquad/latest/miniquad/conf/index.html#high-dpi-rendering> for details.
336    ///
337    /// Always 1.0 if `high_dpi` in `Config` is set to `false`.
338    #[inline]
339    pub fn dpi_scale(&self) -> f32 {
340        window::dpi_scale()
341    }
342
343    /// Time passed between previous and current frame (in seconds).
344    #[inline]
345    pub fn delta_time_secs(&self) -> f64 {
346        self.delta_time
347    }
348
349    /// Time passed between previous and current frame (as [`std::time::Duration`]).
350    #[inline]
351    pub fn delta_time(&self) -> Duration {
352        Duration::from_secs_f64(self.delta_time)
353    }
354
355    /// Set clear/background color.
356    ///
357    /// The framebuffer isn't cleared automatically, use [`Context::clear()`] for that.
358    #[inline]
359    pub fn clear_color(&mut self, color: RGBA8) {
360        self.clear_color = color;
361    }
362
363    /// Returns current input state of a key or `None` if it isn't held.
364    ///
365    /// Note that [`InputState::Released`] means that the key has **just** been released, **not** that it isn't held.
366    #[inline]
367    pub fn get_key_state(&self, key: KeyCode) -> Option<InputState> {
368        self.keys.get(&key).copied()
369    }
370
371    /// Returns all keys that are down or have just been pressed/released.
372    #[inline]
373    pub fn get_all_keys(&self) -> &FxHashMap<KeyCode, InputState> {
374        &self.keys
375    }
376
377    /// Returns `true` if a key is down.
378    #[inline]
379    pub fn is_key_down(&self, key: KeyCode) -> bool {
380        self.get_key_state(key)
381            .map_or(false, |state| state != InputState::Released)
382    }
383
384    /// Returns `true` if a key has just been pressed.
385    #[inline]
386    pub fn is_key_pressed(&self, key: KeyCode) -> bool {
387        self.get_key_state(key)
388            .map_or(false, |state| state == InputState::Pressed)
389    }
390
391    /// Returns `true` if a key has just been released.
392    #[inline]
393    pub fn is_key_released(&self, key: KeyCode) -> bool {
394        self.get_key_state(key)
395            .map_or(false, |state| state == InputState::Released)
396    }
397
398    /// Returns currently held key modifiers.
399    #[inline]
400    pub fn get_key_mods(&self) -> KeyMods {
401        self.key_mods
402    }
403
404    /// Returns current mouse position in the window (in screen coords).
405    #[inline]
406    pub fn get_screen_mouse_pos(&self) -> (f32, f32) {
407        self.mouse_pos
408    }
409
410    /// Returns current mouse position in the window (in framebuffer pixels).
411    #[inline]
412    pub fn get_framebuffer_mouse_pos(&self) -> (i32, i32) {
413        let (x, y) = self.mouse_pos;
414        let (win_width, win_height) = window::screen_size();
415
416        (
417            (x / win_width * self.buf_width as f32) as _,
418            (y / win_height * self.buf_height as f32) as _,
419        )
420    }
421
422    /// Get current mouse wheel movement.
423    #[inline]
424    pub fn get_mouse_wheel(&self) -> (f32, f32) {
425        self.mouse_wheel
426    }
427
428    /// Returns current input state of a mouse button or `None` if it isn't held.
429    ///
430    /// Note that [`InputState::Released`] means that the key has **just** been released, **not** that it isn't held.
431    #[inline]
432    pub fn get_mouse_button_state(&self, button: MouseButton) -> Option<InputState> {
433        self.mouse_buttons.get(&button).copied()
434    }
435
436    /// Returns all mouse buttons that are down or have just been pressed/released.
437    #[inline]
438    pub fn get_all_mouse_buttons(&self) -> &FxHashMap<MouseButton, InputState> {
439        &self.mouse_buttons
440    }
441
442    /// Returns `true` if a mouse button is down.
443    #[inline]
444    pub fn is_mouse_button_down(&self, button: MouseButton) -> bool {
445        self.get_mouse_button_state(button)
446            .map_or(false, |state| state != InputState::Released)
447    }
448
449    /// Returns `true` if a mouse button has just been pressed.
450    #[inline]
451    pub fn is_mouse_button_pressed(&self, button: MouseButton) -> bool {
452        self.get_mouse_button_state(button)
453            .map_or(false, |state| state == InputState::Pressed)
454    }
455
456    /// Returns `true` if a mouse button has just been released.
457    #[inline]
458    pub fn is_mouse_button_released(&self, button: MouseButton) -> bool {
459        self.get_mouse_button_state(button)
460            .map_or(false, |state| state == InputState::Released)
461    }
462
463    /// Quit the application.
464    #[inline]
465    pub fn quit(&self) {
466        window::request_quit();
467    }
468
469    /// Show or hide the mouse cursor.
470    #[inline]
471    pub fn show_mouse(&self, shown: bool) {
472        window::show_mouse(shown);
473    }
474
475    /// Show or hide onscreen keyboard. This only works on Android.
476    #[inline]
477    pub fn show_keyboard(&self, shown: bool) {
478        window::show_keyboard(shown);
479    }
480
481    /// Set the mouse cursor icon.
482    #[inline]
483    pub fn set_mouse_cursor(&self, cursor_icon: CursorIcon) {
484        window::set_mouse_cursor(cursor_icon);
485    }
486
487    /// Set window to fullscreen or not.
488    #[inline]
489    pub fn set_fullscreen(&self, fullscreen: bool) {
490        window::set_fullscreen(fullscreen);
491    }
492
493    /// Get current OS clipboard value.
494    #[inline]
495    pub fn get_clipboard(&self) -> Option<String> {
496        window::clipboard_get()
497    }
498
499    /// Save value to OS clipboard.
500    #[inline]
501    pub fn set_clipboard(&self, data: impl AsRef<str>) {
502        window::clipboard_set(data.as_ref());
503    }
504
505    /// Set the application's window size.
506    ///
507    /// Note: resizing the window does not resize the framebuffer.
508    /// It will be scaled to the whole window.
509    /// You can use [`Context::set_framebuffer_size()`] for resizing the framebuffer.
510    #[inline]
511    pub fn set_window_size(&mut self, new_width: u32, new_height: u32) {
512        window::set_window_size(new_width, new_height);
513    }
514
515    /// Set the framebuffer size. The buffer will be cleared.
516    ///
517    /// This doesn't change the window size.
518    /// The framebuffer will be scaled to the whole window.
519    pub fn set_framebuffer_size(&mut self, new_width: u32, new_height: u32) {
520        // miniquad's `texture_resize` is currently unimplemented on Metal backend so we're doing this awkward dance
521
522        self.backend.delete_texture(self.texture());
523
524        let new_texture = self
525            .backend
526            .new_render_texture(Self::texture_params(new_width, new_height));
527        self.set_texture(new_texture);
528
529        self.buf_width = new_width;
530        self.buf_height = new_height;
531
532        self.framebuffer.fill(self.clear_color);
533        self.framebuffer
534            .resize((new_width * new_height) as usize, self.clear_color);
535    }
536
537    /// Clear the screen framebuffer with the current [`Context::clear_color()`].
538    #[inline]
539    pub fn clear(&mut self) {
540        for pix in self.framebuffer.iter_mut() {
541            *pix = self.clear_color;
542        }
543    }
544
545    /// Draw a pixels at (x, y).
546    ///
547    /// Does nothing if the position is outside the screen.
548    #[inline]
549    pub fn draw_pixel(&mut self, x: i32, y: i32, color: RGBA8) {
550        if let Some(pix) = self
551            .framebuffer
552            .get_mut(y as usize * self.buf_width as usize + x as usize)
553        {
554            *pix = color;
555        }
556    }
557
558    /// Draw a colored rectangle.
559    ///
560    /// Does not panic if a part of the rectangle isn't on screen, just draws the part that is.
561    pub fn draw_rect(&mut self, x: i32, y: i32, width: u32, height: u32, color: RGBA8) {
562        simple_blit::blit(
563            self.as_mut_surface()
564                .offset_surface_mut([x as u32, y as _].into()),
565            simple_blit::SingleValueSurface::new(color, [width, height].into()),
566            &[],
567        );
568    }
569
570    /// Fill a rectangle with provided pixels (row-major order).
571    ///
572    /// Does not panic if a part of the rectangle isn't on screen, just draws the part that is.
573    pub fn draw_pixels(&mut self, x: i32, y: i32, width: u32, height: u32, pixels: &[RGBA8]) {
574        if let Some(buffer) = simple_blit::GenericSurface::new(pixels, [width, height].into()) {
575            simple_blit::blit(
576                self.as_mut_surface()
577                    .offset_surface_mut([x as u32, y as _].into()),
578                buffer.sub_surface([0, 0].into(), [width, height].into()),
579                &[],
580            );
581        }
582    }
583
584    /// Fill the entire screen framebuffer at once.
585    ///
586    /// Does not panic if a part of the rectangle isn't on screen, just draws the part that is.
587    pub fn draw_screen(&mut self, pixels: &[RGBA8]) {
588        if let Some(buffer) = simple_blit::GenericSurface::new(
589            pixels,
590            simple_blit::size(self.buf_width, self.buf_height),
591        ) {
592            simple_blit::blit(self.as_mut_surface(), buffer, &[]);
593        }
594    }
595
596    /// Returns the framebuffer's contents.
597    #[inline]
598    pub fn get_draw_buffer(&self) -> &[RGBA8] {
599        &self.framebuffer
600    }
601
602    /// Returns the framebuffer's contents.
603    ///
604    /// Can be used for drawing.
605    #[inline]
606    pub fn get_mut_draw_buffer(&mut self) -> &mut [RGBA8] {
607        &mut self.framebuffer
608    }
609
610    /// Get the draw framebuffer as a [`simple_blit::GenericSurface`].
611    #[inline]
612    pub fn as_surface(&self) -> GenericSurface<&[RGBA8], RGBA8> {
613        GenericSurface::new(
614            &self.framebuffer[..],
615            simple_blit::size(self.buf_width, self.buf_height),
616        )
617        .unwrap()
618    }
619
620    /// Get the draw framebuffer as a mutable [`simple_blit::GenericSurface`].
621    #[inline]
622    pub fn as_mut_surface(&mut self) -> GenericSurface<&mut [RGBA8], RGBA8> {
623        GenericSurface::new(
624            &mut self.framebuffer[..],
625            simple_blit::size(self.buf_width, self.buf_height),
626        )
627        .unwrap()
628    }
629
630    /// Set the filter for the texture that is used for rendering.
631    #[inline]
632    pub fn set_texture_filter(&mut self, filter: FilterMode) {
633        self.backend
634            .texture_set_filter(self.texture(), filter, MipmapFilterMode::None);
635    }
636
637    /// Get the underlying [`RenderingBackend`](https://docs.rs/miniquad/latest/miniquad/graphics/trait.RenderingBackend.html).
638    #[inline]
639    pub fn get_rendering_backend(&self) -> &dyn RenderingBackend {
640        &*self.backend
641    }
642
643    /// Get the underlying [`RenderingBackend`](https://docs.rs/miniquad/latest/miniquad/graphics/trait.RenderingBackend.html).
644    #[inline]
645    pub fn get_mut_rendering_backend(&mut self) -> &mut dyn RenderingBackend {
646        &mut *self.backend
647    }
648}
649
650/// Application state.
651pub trait App {
652    /// Called every frame.
653    fn update(&mut self, ctx: &mut Context);
654
655    /// Called every frame after `update()`.
656    /// See <https://docs.rs/miniquad/latest/miniquad/trait.EventHandler.html#tymethod.update> for specifics.
657    fn draw(&mut self, ctx: &mut Context);
658}
659
660struct Handler<S: App> {
661    ctx: Context,
662    state: S,
663}
664
665impl<S> EventHandler for Handler<S>
666where
667    S: App,
668{
669    fn update(&mut self) {
670        let new_instant = miniquad::date::now();
671        self.ctx.delta_time = new_instant - self.ctx.instant;
672        self.ctx.instant = new_instant;
673
674        self.state.update(&mut self.ctx);
675
676        self.ctx.mouse_wheel = (0., 0.);
677
678        self.ctx.keys.retain(|_, state| match state {
679            InputState::Down => true,
680            InputState::Pressed => {
681                *state = InputState::Down;
682                true
683            }
684            InputState::Released => false,
685        });
686
687        self.ctx.mouse_buttons.retain(|_, state| match state {
688            InputState::Down => true,
689            InputState::Pressed => {
690                *state = InputState::Down;
691                true
692            }
693            InputState::Released => false,
694        });
695    }
696
697    fn draw(&mut self) {
698        self.state.draw(&mut self.ctx);
699
700        self.ctx
701            .backend
702            .texture_update(self.ctx.texture(), self.ctx.framebuffer.as_bytes());
703
704        self.ctx.backend.begin_default_pass(PassAction::Nothing);
705
706        self.ctx.backend.apply_pipeline(&self.ctx.pipeline);
707        self.ctx.backend.apply_bindings(&self.ctx.bindings);
708
709        self.ctx.backend.draw(0, 6, 1);
710
711        self.ctx.backend.end_render_pass();
712
713        self.ctx.backend.commit_frame();
714    }
715
716    #[inline]
717    fn key_down_event(&mut self, key_code: KeyCode, key_mods: KeyMods, repeat: bool) {
718        if !repeat {
719            self.ctx.keys.insert(key_code, InputState::Pressed);
720        }
721
722        self.ctx.key_mods = key_mods;
723    }
724
725    #[inline]
726    fn key_up_event(&mut self, key_code: KeyCode, key_mods: KeyMods) {
727        self.ctx.keys.insert(key_code, InputState::Released);
728        self.ctx.key_mods = key_mods;
729    }
730
731    #[inline]
732    fn mouse_button_down_event(&mut self, button: MouseButton, _x: f32, _y: f32) {
733        self.ctx.mouse_buttons.insert(button, InputState::Pressed);
734    }
735
736    #[inline]
737    fn mouse_button_up_event(&mut self, button: MouseButton, _x: f32, _y: f32) {
738        self.ctx.mouse_buttons.insert(button, InputState::Pressed);
739    }
740
741    #[inline]
742    fn mouse_motion_event(&mut self, x: f32, y: f32) {
743        self.ctx.mouse_pos = (x, y);
744    }
745
746    #[inline]
747    fn mouse_wheel_event(&mut self, x: f32, y: f32) {
748        self.ctx.mouse_wheel = (x, y);
749    }
750
751    #[inline]
752    fn char_event(&mut self, _character: char, key_mods: KeyMods, _repeat: bool) {
753        self.ctx.key_mods = key_mods;
754    }
755}
756
757/// Start the application using provided config and state.
758#[inline]
759pub fn start(config: Conf, state: impl App + 'static) {
760    miniquad::start(config, move || {
761        Box::new(Handler {
762            ctx: Context::new(),
763            state,
764        })
765    })
766}