#![deny(warnings)]
#![deny(missing_docs)]
use std::fmt::{Display, Formatter};
#[cfg(any(doc, doctest, all(target_arch = "wasm32", feature = "windowing")))]
use std::marker::PhantomData;
use std::rc::Rc;
#[cfg(any(feature = "image-loading", doc, doctest))]
use {
crate::image::ImageFileFormat,
std::io::{BufRead, Seek},
std::path::Path
};
use crate::color::Color;
use crate::dimen::{UVec2, Vec2};
use crate::error::{BacktraceError, ErrorMessage};
use crate::font::FormattedTextBlock;
use crate::glbackend::GLBackend;
#[cfg(not(target_arch = "wasm32"))]
use crate::glbackend::GLBackendGlow;
use crate::glwrapper::{GLContextManager, GLVersion};
use crate::image::{ImageDataType, ImageHandle, ImageSmoothingMode, RawBitmapData};
use crate::renderer2d::Renderer2D;
use crate::shape::{Polygon, Rect, Rectangle, RoundedRectangle};
#[cfg(target_arch = "wasm32")]
use crate::web::WebCanvasElement;
#[cfg(any(doc, doctest, feature = "windowing"))]
use crate::window::WindowHandler;
#[cfg(any(doc, doctest, all(feature = "windowing", not(target_arch = "wasm32"))))]
use crate::window::{
UserEventSender,
WindowCreationError,
WindowCreationOptions,
WindowPosition,
WindowSize
};
#[cfg(any(doc, doctest))]
use crate::window_internal_doctest::{WebCanvasImpl, WindowGlutin};
#[cfg(all(
feature = "windowing",
not(target_arch = "wasm32"),
not(any(doc, doctest))
))]
use crate::window_internal_glutin::WindowGlutin;
#[cfg(all(feature = "windowing", target_arch = "wasm32", not(any(doc, doctest))))]
use crate::window_internal_web::WebCanvasImpl;
pub mod color;
pub mod shape;
pub mod font;
pub mod dimen;
pub mod numeric;
pub mod error;
pub mod image;
pub mod time;
#[cfg(any(doc, doctest, feature = "windowing"))]
pub mod window;
#[cfg(all(
feature = "windowing",
not(target_arch = "wasm32"),
not(any(doc, doctest))
))]
mod window_internal_glutin;
#[cfg(all(feature = "windowing", target_arch = "wasm32", not(any(doc, doctest))))]
mod window_internal_web;
#[cfg(any(doc, doctest))]
mod window_internal_doctest;
#[cfg(target_arch = "wasm32")]
mod web;
mod font_cache;
mod glbackend;
#[cfg(all(feature = "windowing", not(target_arch = "wasm32")))]
mod glutin_winit;
mod glwrapper;
mod renderer2d;
mod texture_packer;
mod utils;
#[derive(Clone, Debug)]
pub struct GLRendererCreationError
{
description: String
}
impl GLRendererCreationError
{
fn msg_with_cause<S, Cause>(description: S, cause: Cause) -> BacktraceError<Self>
where
S: AsRef<str>,
Cause: std::error::Error + 'static
{
BacktraceError::new_with_cause(
Self {
description: description.as_ref().to_string()
},
cause
)
}
#[allow(dead_code)]
fn msg<S>(description: S) -> BacktraceError<Self>
where
S: AsRef<str>
{
BacktraceError::new(Self {
description: description.as_ref().to_string()
})
}
}
impl Display for GLRendererCreationError
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result
{
Display::fmt("GL renderer creation error: ", f)?;
Display::fmt(&self.description, f)
}
}
pub struct GLRenderer
{
context: GLContextManager,
renderer: Graphics2D
}
impl GLRenderer
{
#[cfg(not(target_arch = "wasm32"))]
pub unsafe fn new_for_gl_context<V, F>(
viewport_size_pixels: V,
loader_function: F
) -> Result<Self, BacktraceError<GLRendererCreationError>>
where
V: Into<UVec2>,
F: FnMut(&str) -> *const std::os::raw::c_void
{
let backend =
GLBackendGlow::new(glow::Context::from_loader_function(loader_function));
Self::new_with_gl_backend(
viewport_size_pixels,
Rc::new(backend),
GLVersion::OpenGL2_0
)
}
#[cfg(any(doc, doctest, target_arch = "wasm32"))]
pub fn new_for_web_canvas_by_id<V, S>(
viewport_size_pixels: V,
element_id: S
) -> Result<Self, BacktraceError<GLRendererCreationError>>
where
V: Into<UVec2>,
S: AsRef<str>
{
WebCanvasElement::new_by_id(element_id.as_ref())
.map_err(|err| {
GLRendererCreationError::msg_with_cause("Failed to get canvas", err)
})?
.get_webgl2_context(viewport_size_pixels)
}
fn new_with_gl_backend<V: Into<UVec2>>(
viewport_size_pixels: V,
gl_backend: Rc<dyn GLBackend>,
gl_version: GLVersion
) -> Result<Self, BacktraceError<GLRendererCreationError>>
{
let viewport_size_pixels = viewport_size_pixels.into();
let context =
GLContextManager::create(gl_backend, gl_version).map_err(|err| {
GLRendererCreationError::msg_with_cause(
"GL context manager creation failed",
err
)
})?;
let renderer = Graphics2D {
renderer: Renderer2D::new(&context, viewport_size_pixels).map_err(|err| {
GLRendererCreationError::msg_with_cause("Renderer2D creation failed", err)
})?
};
Ok(GLRenderer { context, renderer })
}
pub fn set_viewport_size_pixels(&mut self, viewport_size_pixels: UVec2)
{
self.renderer
.renderer
.set_viewport_size_pixels(viewport_size_pixels)
}
pub fn create_image_from_raw_pixels(
&mut self,
data_type: ImageDataType,
smoothing_mode: ImageSmoothingMode,
size: UVec2,
data: &[u8]
) -> Result<ImageHandle, BacktraceError<ErrorMessage>>
{
self.renderer
.create_image_from_raw_pixels(data_type, smoothing_mode, size, data)
}
#[cfg(any(feature = "image-loading", doc, doctest))]
pub fn create_image_from_file_path<S: AsRef<Path>>(
&mut self,
data_type: Option<ImageFileFormat>,
smoothing_mode: ImageSmoothingMode,
path: S
) -> Result<ImageHandle, BacktraceError<ErrorMessage>>
{
self.renderer
.create_image_from_file_path(data_type, smoothing_mode, path)
}
#[cfg(any(feature = "image-loading", doc, doctest))]
pub fn create_image_from_file_bytes<R: Seek + BufRead>(
&mut self,
data_type: Option<ImageFileFormat>,
smoothing_mode: ImageSmoothingMode,
file_bytes: R
) -> Result<ImageHandle, BacktraceError<ErrorMessage>>
{
self.renderer
.create_image_from_file_bytes(data_type, smoothing_mode, file_bytes)
}
#[inline]
pub fn draw_frame<F: FnOnce(&mut Graphics2D) -> R, R>(&mut self, callback: F) -> R
{
self.renderer.set_clip(None);
let result = callback(&mut self.renderer);
self.renderer.renderer.finish_frame();
result
}
}
impl Drop for GLRenderer
{
fn drop(&mut self)
{
self.context.mark_invalid();
}
}
pub struct Graphics2D
{
renderer: Renderer2D
}
impl Graphics2D
{
pub fn create_image_from_raw_pixels<S: Into<UVec2>>(
&mut self,
data_type: ImageDataType,
smoothing_mode: ImageSmoothingMode,
size: S,
data: &[u8]
) -> Result<ImageHandle, BacktraceError<ErrorMessage>>
{
self.renderer.create_image_from_raw_pixels(
data_type,
smoothing_mode,
size.into(),
data
)
}
#[cfg(any(feature = "image-loading", doc, doctest))]
pub fn create_image_from_file_path<S: AsRef<Path>>(
&mut self,
data_type: Option<ImageFileFormat>,
smoothing_mode: ImageSmoothingMode,
path: S
) -> Result<ImageHandle, BacktraceError<ErrorMessage>>
{
self.renderer
.create_image_from_file_path(data_type, smoothing_mode, path)
}
#[cfg(any(feature = "image-loading", doc, doctest))]
pub fn create_image_from_file_bytes<R: Seek + BufRead>(
&mut self,
data_type: Option<ImageFileFormat>,
smoothing_mode: ImageSmoothingMode,
file_bytes: R
) -> Result<ImageHandle, BacktraceError<ErrorMessage>>
{
self.renderer
.create_image_from_file_bytes(data_type, smoothing_mode, file_bytes)
}
pub fn clear_screen(&mut self, color: Color)
{
self.renderer.clear_screen(color);
}
pub fn draw_text<V: Into<Vec2>>(
&mut self,
position: V,
color: Color,
text: &FormattedTextBlock
)
{
self.renderer.draw_text(position, color, text);
}
pub fn draw_text_cropped<V: Into<Vec2>>(
&mut self,
position: V,
crop_window: Rect,
color: Color,
text: &FormattedTextBlock
)
{
self.renderer
.draw_text_cropped(position, crop_window, color, text);
}
pub fn draw_polygon<V: Into<Vec2>>(
&mut self,
polygon: &Polygon,
offset: V,
color: Color
)
{
self.renderer.draw_polygon(polygon, offset, color)
}
pub fn draw_triangle_three_color(
&mut self,
vertex_positions_clockwise: [Vec2; 3],
vertex_colors_clockwise: [Color; 3]
)
{
self.renderer.draw_triangle_three_color(
vertex_positions_clockwise,
vertex_colors_clockwise
);
}
pub fn draw_triangle_image_tinted_three_color(
&mut self,
vertex_positions_clockwise: [Vec2; 3],
vertex_colors: [Color; 3],
image_coords_normalized: [Vec2; 3],
image: &ImageHandle
)
{
self.renderer.draw_triangle_image_tinted(
vertex_positions_clockwise,
vertex_colors,
image_coords_normalized,
image
);
}
#[inline]
pub fn draw_triangle(&mut self, vertex_positions_clockwise: [Vec2; 3], color: Color)
{
self.draw_triangle_three_color(vertex_positions_clockwise, [color, color, color]);
}
#[inline]
pub fn draw_quad_four_color(
&mut self,
vertex_positions_clockwise: [Vec2; 4],
vertex_colors: [Color; 4]
)
{
let vp = vertex_positions_clockwise;
let vc = vertex_colors;
self.draw_triangle_three_color([vp[0], vp[1], vp[2]], [vc[0], vc[1], vc[2]]);
self.draw_triangle_three_color([vp[2], vp[3], vp[0]], [vc[2], vc[3], vc[0]]);
}
#[inline]
pub fn draw_quad(&mut self, vertex_positions_clockwise: [Vec2; 4], color: Color)
{
self.draw_quad_four_color(
vertex_positions_clockwise,
[color, color, color, color]
);
}
#[inline]
pub fn draw_quad_image_tinted_four_color(
&mut self,
vertex_positions_clockwise: [Vec2; 4],
vertex_colors: [Color; 4],
image_coords_normalized: [Vec2; 4],
image: &ImageHandle
)
{
let vp = vertex_positions_clockwise;
let vc = vertex_colors;
let ic = image_coords_normalized;
self.draw_triangle_image_tinted_three_color(
[vp[0], vp[1], vp[2]],
[vc[0], vc[1], vc[2]],
[ic[0], ic[1], ic[2]],
image
);
self.draw_triangle_image_tinted_three_color(
[vp[2], vp[3], vp[0]],
[vc[2], vc[3], vc[0]],
[ic[2], ic[3], ic[0]],
image
);
}
#[inline]
pub fn draw_rectangle_image_subset_tinted(
&mut self,
rect: impl AsRef<Rectangle>,
color: Color,
image_coords_normalized: impl AsRef<Rectangle>,
image: &ImageHandle
)
{
let rect = rect.as_ref();
let image_coords_normalized = image_coords_normalized.as_ref();
self.draw_quad_image_tinted_four_color(
[
*rect.top_left(),
rect.top_right(),
*rect.bottom_right(),
rect.bottom_left()
],
[color, color, color, color],
[
*image_coords_normalized.top_left(),
image_coords_normalized.top_right(),
*image_coords_normalized.bottom_right(),
image_coords_normalized.bottom_left()
],
image
);
}
#[inline]
pub fn draw_rectangle_image_tinted(
&mut self,
rect: impl AsRef<Rectangle>,
color: Color,
image: &ImageHandle
)
{
self.draw_rectangle_image_subset_tinted(
rect,
color,
Rectangle::new(Vec2::ZERO, Vec2::new(1.0, 1.0)),
image
);
}
#[inline]
pub fn draw_rectangle_image(
&mut self,
rect: impl AsRef<Rectangle>,
image: &ImageHandle
)
{
self.draw_rectangle_image_tinted(rect, Color::WHITE, image);
}
#[inline]
pub fn draw_image<P: Into<Vec2>>(&mut self, position: P, image: &ImageHandle)
{
let position = position.into();
self.draw_rectangle_image(
Rectangle::new(position, position + image.size().into_f32()),
image
);
}
#[inline]
pub fn draw_rectangle(&mut self, rect: impl AsRef<Rectangle>, color: Color)
{
let rect = rect.as_ref();
self.draw_quad(
[
*rect.top_left(),
rect.top_right(),
*rect.bottom_right(),
rect.bottom_left()
],
color
);
}
#[inline]
pub fn draw_rounded_rectangle(
&mut self,
round_rect: impl AsRef<RoundedRectangle>,
color: Color
)
{
let round_rect = round_rect.as_ref();
self.draw_quad(
[
round_rect.top_left() + Vec2::new(round_rect.radius(), 0.0),
round_rect.top_right() + Vec2::new(-round_rect.radius(), 0.0),
round_rect.bottom_right() + Vec2::new(-round_rect.radius(), 0.0),
round_rect.bottom_left() + Vec2::new(round_rect.radius(), 0.0)
],
color
);
self.draw_quad(
[
round_rect.top_left() + Vec2::new(0.0, round_rect.radius()),
round_rect.top_left()
+ Vec2::new(round_rect.radius(), round_rect.radius()),
round_rect.bottom_left()
+ Vec2::new(round_rect.radius(), -round_rect.radius()),
round_rect.bottom_left() + Vec2::new(0.0, -round_rect.radius())
],
color
);
self.draw_quad(
[
round_rect.top_right() + Vec2::new(0.0, round_rect.radius()),
round_rect.top_right()
+ Vec2::new(-round_rect.radius(), round_rect.radius()),
round_rect.bottom_right()
+ Vec2::new(-round_rect.radius(), -round_rect.radius()),
round_rect.bottom_right() + Vec2::new(0.0, -round_rect.radius())
],
color
);
self.draw_triangle(
[
round_rect.top_left() + Vec2::new(round_rect.radius(), 0.0),
round_rect.top_left()
+ Vec2::new(round_rect.radius(), round_rect.radius()),
round_rect.top_left() + Vec2::new(0.0, round_rect.radius())
],
color
);
self.draw_triangle(
[
round_rect.top_right() + Vec2::new(-round_rect.radius(), 0.0),
round_rect.top_right()
+ Vec2::new(-round_rect.radius(), round_rect.radius()),
round_rect.top_right() + Vec2::new(0.0, round_rect.radius())
],
color
);
self.draw_triangle(
[
round_rect.bottom_left() + Vec2::new(round_rect.radius(), 0.0),
round_rect.bottom_left() + Vec2::new(0.0, -round_rect.radius()),
round_rect.bottom_left()
+ Vec2::new(round_rect.radius(), -round_rect.radius())
],
color
);
self.draw_triangle(
[
round_rect.bottom_right() + Vec2::new(-round_rect.radius(), 0.0),
round_rect.bottom_right()
+ Vec2::new(-round_rect.radius(), -round_rect.radius()),
round_rect.bottom_right() + Vec2::new(0.0, -round_rect.radius())
],
color
);
self.draw_circle_section_triangular_three_color(
[
round_rect.top_right() + Vec2::new(-round_rect.radius(), 0.0),
round_rect.top_right(),
round_rect.top_right() + Vec2::new(0.0, round_rect.radius())
],
[color; 3],
[
Vec2::new(0.0, 1.0),
Vec2::new(1.0, 1.0),
Vec2::new(1.0, 0.0)
]
);
self.draw_circle_section_triangular_three_color(
[
round_rect.top_left() + Vec2::new(0.0, round_rect.radius()),
*round_rect.top_left(),
round_rect.top_left() + Vec2::new(round_rect.radius(), 0.0)
],
[color; 3],
[
Vec2::new(-1.0, 0.0),
Vec2::new(-1.0, 1.0),
Vec2::new(0.0, 1.0)
]
);
self.draw_circle_section_triangular_three_color(
[
round_rect.bottom_left() + Vec2::new(round_rect.radius(), 0.0),
round_rect.bottom_left(),
round_rect.bottom_left() + Vec2::new(0.0, -round_rect.radius())
],
[color; 3],
[
Vec2::new(0.0, -1.0),
Vec2::new(-1.0, -1.0),
Vec2::new(-1.0, 0.0)
]
);
self.draw_circle_section_triangular_three_color(
[
round_rect.bottom_right() + Vec2::new(0.0, -round_rect.radius()),
*round_rect.bottom_right(),
round_rect.bottom_right() + Vec2::new(-round_rect.radius(), 0.0)
],
[color; 3],
[
Vec2::new(1.0, 0.0),
Vec2::new(1.0, -1.0),
Vec2::new(0.0, -1.0)
]
);
}
pub fn draw_line<VStart: Into<Vec2>, VEnd: Into<Vec2>>(
&mut self,
start_position: VStart,
end_position: VEnd,
thickness: f32,
color: Color
)
{
let start_position = start_position.into();
let end_position = end_position.into();
let gradient_normalized = match (end_position - start_position).normalize() {
None => return,
Some(gradient) => gradient
};
let gradient_thickness = gradient_normalized * (thickness / 2.0);
let offset_anticlockwise = gradient_thickness.rotate_90_degrees_anticlockwise();
let offset_clockwise = gradient_thickness.rotate_90_degrees_clockwise();
let start_anticlockwise = start_position + offset_anticlockwise;
let start_clockwise = start_position + offset_clockwise;
let end_anticlockwise = end_position + offset_anticlockwise;
let end_clockwise = end_position + offset_clockwise;
self.draw_quad(
[
start_anticlockwise,
end_anticlockwise,
end_clockwise,
start_clockwise
],
color
);
}
pub fn draw_circle<V: Into<Vec2>>(
&mut self,
center_position: V,
radius: f32,
color: Color
)
{
let center_position = center_position.into();
let top_left = center_position + Vec2::new(-radius, -radius);
let top_right = center_position + Vec2::new(radius, -radius);
let bottom_right = center_position + Vec2::new(radius, radius);
let bottom_left = center_position + Vec2::new(-radius, radius);
self.renderer.draw_circle_section(
[top_left, top_right, bottom_right],
[color, color, color],
[
Vec2::new(-1.0, -1.0),
Vec2::new(1.0, -1.0),
Vec2::new(1.0, 1.0)
]
);
self.renderer.draw_circle_section(
[bottom_right, bottom_left, top_left],
[color, color, color],
[
Vec2::new(1.0, 1.0),
Vec2::new(-1.0, 1.0),
Vec2::new(-1.0, -1.0)
]
);
}
#[inline]
pub fn draw_circle_section_triangular_three_color(
&mut self,
vertex_positions_clockwise: [Vec2; 3],
vertex_colors: [Color; 3],
vertex_circle_coords_normalized: [Vec2; 3]
)
{
self.renderer.draw_circle_section(
vertex_positions_clockwise,
vertex_colors,
vertex_circle_coords_normalized
);
}
pub fn set_clip(&mut self, rect: Option<Rectangle<i32>>)
{
self.renderer.set_clip(rect);
}
pub fn capture(&mut self, format: ImageDataType) -> RawBitmapData
{
self.renderer.capture(format)
}
}
#[cfg(any(doc, doctest, all(feature = "windowing", not(target_arch = "wasm32"))))]
pub struct Window<UserEventType = ()>
where
UserEventType: 'static
{
window_impl: WindowGlutin<UserEventType>,
renderer: GLRenderer
}
#[cfg(any(doc, doctest, all(feature = "windowing", not(target_arch = "wasm32"))))]
impl Window<()>
{
pub fn new_centered<Str, Size>(
title: Str,
size: Size
) -> Result<Window<()>, BacktraceError<WindowCreationError>>
where
Str: AsRef<str>,
Size: Into<UVec2>
{
let size = size.into();
Self::new_with_options(
title.as_ref(),
WindowCreationOptions::new_windowed(
WindowSize::PhysicalPixels(size),
Some(WindowPosition::Center)
)
)
}
pub fn new_fullscreen_borderless<Str>(
title: Str
) -> Result<Window<()>, BacktraceError<WindowCreationError>>
where
Str: AsRef<str>
{
Self::new_with_options(
title.as_ref(),
WindowCreationOptions::new_fullscreen_borderless()
)
}
pub fn new_with_options(
title: &str,
options: WindowCreationOptions
) -> Result<Window<()>, BacktraceError<WindowCreationError>>
{
Self::new_with_user_events(title, options)
}
}
#[cfg(any(doc, doctest, all(feature = "windowing", not(target_arch = "wasm32"))))]
impl<UserEventType: 'static> Window<UserEventType>
{
pub fn new_with_user_events(
title: &str,
options: WindowCreationOptions
) -> Result<Self, BacktraceError<WindowCreationError>>
{
let window_impl = WindowGlutin::new(title, options)?;
let renderer = GLRenderer::new_with_gl_backend(
window_impl.get_inner_size_pixels(),
window_impl.gl_backend().clone(),
GLVersion::OpenGL2_0
)
.map_err(|err| {
BacktraceError::new_with_cause(
WindowCreationError::RendererCreationFailed,
err
)
})?;
Ok(Window {
window_impl,
renderer
})
}
pub fn create_user_event_sender(&self) -> UserEventSender<UserEventType>
{
self.window_impl.create_user_event_sender()
}
pub fn run_loop<H>(self, handler: H) -> !
where
H: WindowHandler<UserEventType> + 'static
{
self.window_impl.run_loop(handler, self.renderer);
}
}
#[cfg(any(doc, doctest, all(target_arch = "wasm32", feature = "windowing")))]
pub struct WebCanvas<UserEventType = ()>
where
UserEventType: 'static
{
inner: Option<WebCanvasImpl>,
should_cleanup: bool,
user_event_type: PhantomData<UserEventType>
}
#[cfg(any(doc, doctest, all(target_arch = "wasm32", feature = "windowing")))]
impl WebCanvas<()>
{
pub fn new_for_id<S, H>(
element_id: S,
handler: H
) -> Result<WebCanvas<()>, BacktraceError<ErrorMessage>>
where
S: AsRef<str>,
H: WindowHandler<()> + 'static
{
WebCanvas::<()>::new_for_id_with_user_events(element_id, handler)
}
}
#[cfg(any(doc, doctest, all(target_arch = "wasm32", feature = "windowing")))]
impl<UserEventType: 'static> WebCanvas<UserEventType>
{
pub fn new_for_id_with_user_events<S, H>(
element_id: S,
handler: H
) -> Result<Self, BacktraceError<ErrorMessage>>
where
S: AsRef<str>,
H: WindowHandler<UserEventType> + 'static
{
Ok(WebCanvas {
inner: Some(WebCanvasImpl::new(element_id, handler)?),
should_cleanup: false,
user_event_type: PhantomData
})
}
pub fn unregister_when_dropped(&mut self)
{
self.should_cleanup = true;
}
}
#[cfg(any(doc, doctest, all(target_arch = "wasm32", feature = "windowing")))]
impl<UserEventType: 'static> Drop for WebCanvas<UserEventType>
{
fn drop(&mut self)
{
if !self.should_cleanup {
std::mem::forget(self.inner.take());
log::info!(
"Deliberately leaking speedy2d::WebCanvas object. This is normally \
fine, but if you want to clean up before the page closes, call \
WebCanvas::unregister_when_dropped(), and retain ownership of the \
WebCanvas until you want to delete it."
)
}
}
}