use std::{collections::HashSet, sync::{Arc, atomic::{AtomicUsize, Ordering}}};
use wgpu::{CurrentSurfaceTexture, Extent3d, LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, StoreOp, Surface, SurfaceConfiguration, SurfaceTexture};
use winit::{dpi::{PhysicalPosition, PhysicalSize}, monitor::Fullscreen, window::WindowLevel};
use crate::{AdvancedWindowProperties, GpuContext, Renderer, RendererResult, canvas::{Canvas, RenderSurface}, image::Image, shapes::ScalingMode, text::{Font, HorizontalAlign, Span, VerticalAlign}, transform::Transform2d, types::Color, vec::Vec2, view::ViewMode};
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WindowId(pub(crate) usize);
impl WindowId {
pub(crate) fn new() -> Self {
Self(NEXT_ID.fetch_add(1, Ordering::Relaxed))
}
}
#[derive(Debug, Clone)]
pub struct WindowProperties {
pub title: String,
pub width: u32,
pub height: u32,
pub resizable: bool,
pub transparent: bool,
pub fullscreen: bool,
pub maximized: bool,
pub always_on_top: bool,
}
impl WindowProperties {
pub fn new(title: impl Into<String>, width: u32, height: u32) -> Self {
Self {
title: title.into(),
width,
height,
..Default::default()
}
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = title.into();
self
}
pub fn width(mut self, width: u32) -> Self {
self.width = width;
self
}
pub fn height(mut self, height: u32) -> Self {
self.height = height;
self
}
pub fn size(mut self, width: u32, height: u32) -> Self {
self.width = width;
self.height = height;
self
}
pub fn resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
self
}
pub fn transparent(mut self, transparent: bool) -> Self {
self.transparent = transparent;
self
}
pub fn fullscreen(mut self, fullscreen: bool) -> Self {
self.fullscreen = fullscreen;
self
}
pub fn maximized(mut self, maximized: bool) -> Self {
self.maximized = maximized;
self
}
pub fn always_on_top(mut self, always_on_top: bool) -> Self {
self.always_on_top = always_on_top;
self
}
pub fn build(self, renderer: &mut Renderer) -> WindowId {
renderer.create_window_ext(self)
}
}
impl From<WindowProperties> for AdvancedWindowProperties {
fn from(props: WindowProperties) -> Self {
let mut attributes = AdvancedWindowProperties::default()
.with_title(props.title)
.with_surface_size(PhysicalSize::new(props.width, props.height))
.with_resizable(props.resizable)
.with_transparent(props.transparent)
.with_maximized(props.maximized);
if props.fullscreen {
attributes = attributes.with_fullscreen(Some(Fullscreen::Borderless(None)));
}
if props.always_on_top {
attributes = attributes.with_window_level(WindowLevel::AlwaysOnTop);
}
attributes
}
}
impl Default for WindowProperties {
fn default() -> Self {
Self {
title: "verdant window".into(),
width: 800,
height: 600,
resizable: false,
transparent: false,
fullscreen: false,
maximized: false,
always_on_top: false,
}
}
}
#[derive(Debug, Default)]
pub(crate) struct WindowContext {
pub mouse_x: f64,
pub mouse_y: f64,
pub focused: bool,
}
pub struct Window {
pub(crate) inner_window: Arc<Box<dyn winit::window::Window>>,
canvas: Canvas,
surface: Surface<'static>,
config: SurfaceConfiguration,
gpu_context: Arc<GpuContext>,
context: WindowContext,
}
impl Window {
pub(crate) fn new(
inner_window: Arc<Box<dyn winit::window::Window>>,
surface: Surface<'static>,
config: SurfaceConfiguration,
gpu_context: Arc<GpuContext>,
) -> Self {
Self {
inner_window,
canvas: Canvas::new(config.width, config.height, true),
surface,
config,
gpu_context,
context: WindowContext::default(),
}
}
pub(crate) fn get_frame(&self) -> Option<SurfaceTexture> {
match self.surface.get_current_texture() {
CurrentSurfaceTexture::Success(tex)
| CurrentSurfaceTexture::Suboptimal(tex) => Some(tex),
CurrentSurfaceTexture::Outdated
| CurrentSurfaceTexture::Lost => {
self.surface.configure(&self.gpu_context.device, &self.config);
None
}
_ => None
}
}
pub(crate) fn present_blank_frame(&self) -> RendererResult<()> {
let Some(frame) = (0..3).find_map(|_| self.get_frame()) else {
return Ok(());
};
let view = frame.texture.create_view(&Default::default());
let mut encoder = self.gpu_context.device.create_command_encoder(&Default::default());
encoder.begin_render_pass(&RenderPassDescriptor {
color_attachments: &[Some(RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: Operations {
load: LoadOp::Clear(Color::BLACK.into()),
store: StoreOp::Store,
},
depth_slice: None,
})],
..Default::default()
});
self.gpu_context.queue.submit([encoder.finish()]);
frame.present();
Ok(())
}
pub(crate) fn on_resize(&mut self, size: PhysicalSize<u32>) {
if size.width == 0 || size.height == 0 { return; }
self.config.width = size.width;
self.config.height = size.height;
self.surface.configure(&self.gpu_context.device, &self.config);
self.canvas.write().resize(size.width, size.height);
}
pub(crate) fn on_mouse_move(&mut self, position: PhysicalPosition<f64>) {
self.context.mouse_x = position.x;
self.context.mouse_y = position.y;
}
pub(crate) fn on_focus_update(&mut self, focused: bool) {
self.context.focused = focused;
}
pub fn get_width(&self) -> f32 {
self.canvas.read().view.window_size().x
}
pub fn get_height(&self) -> f32 {
self.canvas.read().view.window_size().y
}
pub fn get_size(&self) -> Vec2 {
self.canvas.read().view.window_size()
}
pub fn get_mouse_x(&self) -> f32 {
let letterbox = self.canvas.read().view.letterbox();
(self.context.mouse_x as f32 - letterbox.2) / letterbox.0 - self.canvas.read().view.origin().x
}
pub fn get_mouse_y(&self) -> f32 {
let letterbox = self.canvas.read().view.letterbox();
(self.context.mouse_y as f32 - letterbox.3) / letterbox.1 - self.canvas.read().view.origin().y
}
pub fn get_mouse_pos(&self) -> Vec2 {
Vec2::new(self.get_mouse_x(), self.get_mouse_y())
}
pub fn get_raw_mouse_x(&self) -> f32 {
self.context.mouse_x as f32
}
pub fn get_raw_mouse_y(&self) -> f32 {
self.context.mouse_y as f32
}
pub fn get_raw_mouse_pos(&self) -> Vec2 {
Vec2::new(self.get_raw_mouse_x(), self.get_raw_mouse_y())
}
pub fn is_focused(&self) -> bool {
self.context.focused
}
pub fn get_title(&mut self) -> String {
self.inner_window.title()
}
pub fn set_title(&mut self, title: impl ToString) {
self.inner_window.set_title(&title.to_string());
}
}
impl RenderSurface for Window {
fn background(&mut self, color: Color) {
self.canvas.write().background(color);
}
fn fill(&mut self, color: Color) {
self.canvas.write().fill(color);
}
fn no_fill(&mut self) {
self.canvas.write().no_fill();
}
fn outline_color(&mut self, color: Color) {
self.canvas.write().outline_color(color);
}
fn outline_width(&mut self, width: f32) {
self.canvas.write().outline_width(width);
}
fn outline(&mut self, color: Color, width: f32) {
self.canvas.write().outline(color, width);
}
fn outline_style(&mut self, color: Color, width: f32, scaling: ScalingMode) {
self.canvas.write().outline_style(color, width, scaling);
}
fn no_outline(&mut self) {
self.canvas.write().no_outline();
}
fn outline_scaling(&mut self, scaling: ScalingMode) {
self.canvas.write().outline_scaling(scaling);
}
fn corner_radius(&mut self, radius: f32) {
self.canvas.write().corner_radius(radius);
}
fn corner_scaling(&mut self, scaling: ScalingMode) {
self.canvas.write().corner_scaling(scaling);
}
fn corner_style(&mut self, radius: f32, scaling: ScalingMode) {
self.canvas.write().corner_style(radius, scaling);
}
fn scaling_modes(&mut self, outline_scaling: ScalingMode, corner_scaling: ScalingMode) {
self.canvas.write().scaling_modes(outline_scaling, corner_scaling);
}
fn clear_style(&mut self) {
self.canvas.write().clear_style();
}
fn rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
self.canvas.write().rect(x, y, w, h);
}
fn ellipse(&mut self, x: f32, y: f32, rx: f32, ry: f32) {
self.canvas.write().ellipse(x, y, rx, ry);
}
fn line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) {
self.canvas.write().line(x1, y1, x2, y2);
}
fn image(&mut self, image: impl AsRef<Image>, x: f32, y: f32, w: f32, h: f32) {
self.canvas.write().image(image, x, y, w, h);
}
fn composite(&mut self, canvas: impl AsRef<Canvas>, x: f32, y: f32, w: f32, h: f32) {
self.canvas.write().composite(canvas, x, y, w, h);
}
fn horizontal_text_align(&mut self, align: HorizontalAlign) {
self.canvas.write().horizontal_text_align(align);
}
fn vertical_text_align(&mut self, align: VerticalAlign) {
self.canvas.write().vertical_text_align(align);
}
fn text_align(&mut self, horizontal: HorizontalAlign, vertical: VerticalAlign) {
self.canvas.write().text_align(horizontal, vertical);
}
fn line_align(&mut self, align: HorizontalAlign) {
self.canvas.write().line_align(align);
}
fn text_size(&mut self, size_px: f32) {
self.canvas.write().text_size(size_px);
}
fn text(&mut self, font: impl AsRef<Font>, x: f32, y: f32, text: impl ToString) {
self.canvas.write().text(font, x, y, text);
}
fn rich_text(&mut self, x: f32, y: f32, spans: &[Span]) {
self.canvas.write().rich_text(x, y, spans);
}
fn set_view(&mut self, width: f32, height: f32, view_mode: ViewMode) {
self.canvas.write().set_view(width, height, view_mode);
}
fn clear_view(&mut self) {
self.canvas.write().clear_view();
}
fn set_origin(&mut self, x: f32, y: f32) {
self.canvas.write().set_origin(x, y);
}
fn clear_origin(&mut self) {
self.canvas.write().clear_origin();
}
fn with_style(&mut self, commands: impl FnOnce(&mut Self)) {
let (style, text_style, view) = {
let inner = self.canvas.read();
(inner.style, inner.text_style, inner.view)
};
commands(self);
let mut inner = self.canvas.write();
inner.style = style;
inner.text_style = text_style;
inner.view.set(view);
inner.sync_view_transform();
}
fn with_transform(&mut self, transform: impl AsRef<Transform2d>, commands: impl FnOnce(&mut Self)) {
let old_local = {
let mut inner = self.canvas.write();
let old_local = inner.context.local_transform;
let new_local = old_local * *transform.as_ref();
let transform = inner.view.transform();
inner.context.local_transform = new_local;
inner.context.update_transform(transform * new_local);
old_local
};
commands(self);
let mut inner = self.canvas.write();
inner.context.local_transform = old_local;
let transform = inner.view.transform();
inner.context.update_transform(transform * old_local);
}
fn flush(&mut self) -> RendererResult<()> {
let mut encoder = self.gpu_context.device.create_command_encoder(&Default::default());
let Some(frame) = self.get_frame() else { return Ok(()) };
{
let mut root_canvas = self.canvas.write();
root_canvas.flush_with_encoder(&mut encoder, self.gpu_context.clone(), &mut HashSet::new(), self.config.format)?;
}
let Some(canvas_texture) = self.canvas.read().get_texture() else { return Ok(()) };
encoder.copy_texture_to_texture(
canvas_texture.as_image_copy(),
frame.texture.as_image_copy(),
Extent3d {
width: self.config.width,
height: self.config.height,
depth_or_array_layers: 1,
}
);
self.gpu_context.queue.submit([encoder.finish()]);
frame.present();
self.inner_window.request_redraw();
Ok(())
}
}