use instant::{Duration, Instant};
use piet::kurbo::{Affine, BezPath, Point, Rect, Vec2};
use piet::{FontFamily, GradientStop, RenderContext as _, Text, TextLayoutBuilder};
use theo::{Display, RenderContext};
use winit::dpi::LogicalSize;
use winit::event::{Event, WindowEvent};
use winit::event_loop::EventLoop;
use winit::window::WindowBuilder;
const TEST_IMAGE: &[u8] = include_bytes!("./assets/test-image.png");
#[allow(unused_assignments)]
fn main() -> ! {
#[cfg(target_arch = "wasm32")]
{
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
}
env_logger::init();
let window_builder = || {
WindowBuilder::new()
.with_title("theo winit example")
.with_inner_size(LogicalSize::new(800.0, 600.0))
.with_transparent(true)
};
let event_loop = EventLoop::new();
let mut window = None;
let mut display = {
#[allow(unused_mut)]
let mut display = Display::builder();
display = display.transparent(false);
#[cfg(windows)]
{
let start = window_builder()
.build(&event_loop)
.expect("Failed to create window");
display = display.window(&start);
window = Some(start);
}
#[cfg(x11_platform)]
{
display = display.glx_error_hook(winit::platform::x11::register_xlib_error_hook);
}
unsafe {
display
.build(&*event_loop)
.expect("Failed to create display")
}
};
let mut state = None;
let framerate = Duration::from_millis({
let fraction = 1.0 / 60.0;
let millis_per_frame = fraction * 1_000.0;
millis_per_frame as u64
});
let mut next_frame = Instant::now() + framerate;
let star = generate_five_pointed_star((0.0, 0.0).into(), 75.0, 150.0);
let mut fill_brush = None;
let mut stroke_brush = None;
let mut radial_gradient = None;
let mut linear_gradient = None;
let mut tick = 0;
let mut image_handle = None;
let mut last_second = Instant::now();
let mut num_frames = 0;
let mut current_fps = None;
let image = image::load_from_memory(TEST_IMAGE)
.expect("Failed to load test image")
.to_rgba8();
let image_size = image.dimensions();
let image_data = image.into_raw();
let mut draw = move |render_context: &mut RenderContext<'_, '_>| {
use piet::Color;
render_context.clear(None, Color::rgb8(0x87, 0xce, 0xeb));
let fill_brush = fill_brush.get_or_insert_with(|| render_context.solid_brush(Color::RED));
let stroke_brush =
stroke_brush.get_or_insert_with(|| render_context.solid_brush(Color::BLACK));
let radial_gradient = radial_gradient.get_or_insert_with(|| {
let grad = piet::FixedRadialGradient {
center: Point::new(0.0, 0.0),
origin_offset: Vec2::new(0.0, 0.0),
radius: 150.0,
stops: vec![
GradientStop {
pos: 0.0,
color: piet::Color::LIME,
},
GradientStop {
pos: 0.5,
color: piet::Color::MAROON,
},
GradientStop {
pos: 1.0,
color: piet::Color::NAVY,
},
],
};
render_context.gradient(grad).unwrap()
});
let linear_gradient = linear_gradient.get_or_insert_with(|| {
const RAINBOW: &[piet::Color] = &[
piet::Color::RED,
piet::Color::rgb8(0xff, 0x7f, 0x00),
piet::Color::YELLOW,
piet::Color::GREEN,
piet::Color::BLUE,
piet::Color::rgb8(0x4b, 0x00, 0x82),
piet::Color::rgb8(0x94, 0x00, 0xd3),
];
let grad = piet::FixedLinearGradient {
start: Point::new(0.0, 0.0),
end: Point::new(50.0, 150.0),
stops: RAINBOW
.iter()
.enumerate()
.map(|(i, &color)| GradientStop {
pos: i as f32 / (RAINBOW.len() - 1) as f32,
color,
})
.collect(),
};
render_context.gradient(grad).unwrap()
});
let rotation = {
let angle_rad = (tick as f64) * 0.02;
Affine::rotate(angle_rad)
};
let translation = Affine::translate((200.0, 200.0));
render_context.with_save(|render_context| {
render_context.transform(translation * rotation);
render_context.fill(&star, fill_brush);
render_context.stroke(&star, stroke_brush, 5.0);
Ok(())
})?;
let translation = Affine::translate((550.0, 200.0));
let rotation = {
let angle_rad = (tick as f64) * 0.04;
Affine::rotate(angle_rad)
};
let scaling = Affine::scale(0.75);
render_context.with_save(|render_context| {
render_context.transform(translation * rotation * scaling);
render_context.fill(&star, radial_gradient);
render_context.stroke(&star, stroke_brush, 5.0);
Ok(())
})?;
let image_handle = image_handle
.get_or_insert_with(|| {
render_context.make_image(
image_size.0 as _,
image_size.1 as _,
&image_data,
piet::ImageFormat::RgbaSeparate,
)
})
.as_ref()
.unwrap();
let scale = |x: f64| (x + 1.0) * 25.0;
let posn_shift_x = scale(((tick as f64) / 25.0).cos());
let posn_shift_y = scale(((tick as f64) / 25.0).sin());
let posn_x = 400.0 + posn_shift_x;
let posn_y = 400.0 + posn_shift_y;
let size_shift_x = 50.0 + scale(((tick as f64) / 50.0).sin());
let size_shift_y = 50.0 + scale(((tick as f64) / 50.0).cos());
render_context.draw_image(
image_handle,
Rect::new(posn_x, posn_y, posn_x + size_shift_x, posn_y + size_shift_y),
piet::InterpolationMode::Bilinear,
);
let out_rect = Rect::new(100.0, 400.0, 200.0, 500.0);
render_context.draw_image_area(
image_handle,
Rect::new(
25.0 + posn_shift_x,
25.0 + posn_shift_y,
75.0 + posn_shift_x,
75.0 + posn_shift_y,
),
out_rect,
piet::InterpolationMode::Bilinear,
);
render_context.stroke(out_rect, stroke_brush, 3.0);
let rect = Rect::new(0.0, 0.0, 50.0, 150.0);
render_context
.with_save(|render_context| {
let transform = Affine::translate((650.0, 275.0));
render_context.transform(transform);
render_context.fill(rect, linear_gradient);
render_context.stroke(rect, stroke_brush, 5.0);
Ok(())
})
.unwrap();
if cfg!(not(target_arch = "wasm32")) {
num_frames += 1;
let now = Instant::now();
if now - last_second >= Duration::from_secs(1) {
let fps_string = format!("Frames per Second: {num_frames}");
let fps_text = render_context
.text()
.new_text_layout(fps_string)
.font(FontFamily::SERIF, 24.0)
.text_color(piet::Color::BLACK)
.default_attribute(piet::TextAttribute::Underline(true))
.build()
.unwrap();
current_fps = Some(fps_text);
num_frames = 0;
last_second = now;
}
if let Some(fps_text) = ¤t_fps {
render_context.draw_text(fps_text, Point::new(10.0, 10.0));
}
}
tick += 1;
render_context.finish()?;
render_context.status()
};
event_loop.run(move |event, elwt, control_flow| {
control_flow.set_wait_until(next_frame);
match event {
Event::Resumed => {
let window = window.take().unwrap_or_else(|| {
let mut window_builder = window_builder();
if !display.supports_transparency() {
window_builder = window_builder.with_transparent(false);
}
#[cfg(x11_platform)]
{
use winit::platform::x11::WindowBuilderExtX11;
if let Some(visual) = display.x11_visual() {
window_builder = window_builder.with_x11_visual(visual.as_ptr());
}
}
window_builder.build(elwt).expect("Failed to create window")
});
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowExtWebSys;
web_sys::window()
.unwrap()
.document()
.unwrap()
.body()
.unwrap()
.append_child(&window.canvas())
.unwrap();
}
let size = window.inner_size();
let surface = futures_lite::future::block_on(unsafe {
display.make_surface(&window, size.width, size.height)
})
.expect("Failed to create surface");
state = Some((window, surface));
}
Event::Suspended => {
state.take();
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawEventsCleared => {
if Instant::now() < next_frame {
return;
}
if let Some((window, surface)) = &mut state {
let size = window.inner_size();
let mut render_context =
RenderContext::new(&mut display, surface, size.width, size.height)
.expect("Failed to create render context");
draw(&mut render_context).expect("Failed to draw");
drop(render_context);
futures_lite::future::block_on(display.present());
}
next_frame += framerate;
control_flow.set_wait_until(next_frame);
}
_ => {}
}
});
}
fn generate_five_pointed_star(center: Point, inner_radius: f64, outer_radius: f64) -> BezPath {
let point_from_polar = |radius: f64, angle: f64| {
let x = center.x + radius * angle.cos();
let y = center.y + radius * angle.sin();
Point::new(x, y)
};
let one_fifth_circle = std::f64::consts::PI * 2.0 / 5.0;
let outer_points = (0..5).map(|i| point_from_polar(outer_radius, one_fifth_circle * i as f64));
let inner_points = (0..5).map(|i| {
point_from_polar(
inner_radius,
one_fifth_circle * i as f64 + one_fifth_circle / 2.0,
)
});
let mut points = outer_points.zip(inner_points).flat_map(|(a, b)| [a, b]);
let mut path = BezPath::new();
path.move_to(points.next().unwrap());
for point in points {
path.line_to(point);
}
path.close_path();
path
}