#[cfg(feature = "gpu")]
mod gpu;
mod keymap;
#[cfg(target_os = "macos")]
mod macos;
mod owner;
mod runner;
#[cfg(not(target_os = "macos"))]
mod surface;
use std::time::Duration;
use denise::{BufferAge, DamageTracker, Frame, InputEvent, Pen, Point, Rect, Size};
use winit::event_loop::{EventLoop, EventLoopProxy};
use runner::Runner;
#[cfg(feature = "gpu")]
pub use gpu::GpuSurface;
#[cfg(target_os = "macos")]
pub use macos::MacSurface;
#[cfg(not(target_os = "macos"))]
pub use surface::WinitSurface;
#[cfg(target_os = "macos")]
type PlatformSurface = MacSurface;
#[cfg(not(target_os = "macos"))]
type PlatformSurface = WinitSurface;
const LINE_HEIGHT_PX: f32 = 16.0;
#[derive(Debug)]
pub enum Error {
EventLoop(winit::error::EventLoopError),
Window(winit::error::OsError),
#[cfg(not(target_os = "macos"))]
Softbuffer(softbuffer::SoftBufferError),
Surface(denise::SurfaceError),
Present(String),
Gpu(String),
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::EventLoop(err) => write!(f, "event loop: {err}"),
Self::Window(err) => write!(f, "window creation: {err}"),
#[cfg(not(target_os = "macos"))]
Self::Softbuffer(err) => write!(f, "softbuffer: {err}"),
Self::Surface(err) => core::fmt::Display::fmt(err, f),
Self::Present(msg) => write!(f, "present: {msg}"),
Self::Gpu(msg) => write!(f, "gpu: {msg}"),
}
}
}
impl core::error::Error for Error {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::EventLoop(err) => Some(err),
Self::Window(err) => Some(err),
#[cfg(not(target_os = "macos"))]
Self::Softbuffer(err) => Some(err),
Self::Surface(err) => core::error::Error::source(err),
Self::Present(_) | Self::Gpu(_) => None,
}
}
}
impl From<winit::error::EventLoopError> for Error {
fn from(err: winit::error::EventLoopError) -> Self {
Self::EventLoop(err)
}
}
impl From<winit::error::OsError> for Error {
fn from(err: winit::error::OsError) -> Self {
Self::Window(err)
}
}
#[cfg(not(target_os = "macos"))]
impl From<softbuffer::SoftBufferError> for Error {
fn from(err: softbuffer::SoftBufferError) -> Self {
Self::Softbuffer(err)
}
}
impl From<denise::SurfaceError> for Error {
fn from(err: denise::SurfaceError) -> Self {
Self::Surface(err)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Present {
#[default]
Software,
Gpu,
GpuOrSoftware,
}
#[derive(Clone, Debug)]
pub struct WindowConfig {
pub title: String,
pub size: Size,
pub resizable: bool,
pub frame_interval: Duration,
pub present: Present,
pub fill_monitor: bool,
pub position: Option<Point>,
pub maximized: bool,
pub app_id: Option<String>,
}
impl Default for WindowConfig {
fn default() -> Self {
Self {
title: "Denise".into(),
size: Size::new(800, 480),
resizable: true,
frame_interval: Duration::from_nanos(1_000_000_000 / 60),
present: Present::Software,
fill_monitor: false,
position: None,
maximized: false,
app_id: None,
}
}
}
pub trait DeniseApp {
fn update(&mut self, events: &[InputEvent], damage: &mut DamageTracker);
fn render(&mut self, frame: &mut Frame<'_>, damage: &[Rect]) {
let age = frame.age();
let mut canvas = denise_render::Canvas::new(frame);
let drawn = self.paint(&mut canvas.pen(), age, damage);
assert!(
drawn,
"a DeniseApp must implement `render` or `paint`; this one implements neither"
);
}
fn paint(&mut self, pen: &mut Pen<'_>, age: BufferAge, damage: &[Rect]) -> bool {
let _ = (pen, age, damage);
false
}
fn exit_requested(&self) -> bool {
false
}
fn title(&self) -> Option<&str> {
None
}
fn next_frame_in(&self) -> Option<Duration> {
Some(Duration::ZERO)
}
fn close_requested(&mut self) -> bool {
true
}
fn exiting(&mut self) {}
fn set_waker(&mut self, waker: Waker) {
let _ = waker;
}
fn presenting(&mut self, present: Present) {
let _ = present;
}
fn take_windows(&mut self) -> Vec<WindowRequest> {
Vec::new()
}
}
pub fn run<A: DeniseApp + 'static>(config: WindowConfig, app: A) -> Result<(), Error> {
run_with(config, move |_, _| app)
}
pub fn run_with<A, B>(config: WindowConfig, build: B) -> Result<(), Error>
where
A: DeniseApp + 'static,
B: FnOnce(Size, f32) -> A + 'static,
{
let event_loop = EventLoop::with_user_event().build()?;
let waker = Waker(event_loop.create_proxy());
let mut runner = Runner::new(config, boxed(build), waker);
event_loop.run_app(&mut runner)?;
match runner.error {
Some(err) => Err(err),
None => Ok(()),
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Modality {
#[default]
Owned,
Modal,
Independent,
}
pub struct WindowRequest {
pub config: WindowConfig,
pub modality: Modality,
pub(crate) build: Build,
}
impl WindowRequest {
pub fn new<A, B>(config: WindowConfig, build: B) -> Self
where
A: DeniseApp + 'static,
B: FnOnce(Size, f32) -> A + 'static,
{
Self {
config,
modality: Modality::default(),
build: boxed(build),
}
}
pub fn ready<A: DeniseApp + 'static>(config: WindowConfig, app: A) -> Self {
Self::new(config, move |_, _| app)
}
#[must_use]
pub fn with_modality(self, modality: Modality) -> Self {
Self { modality, ..self }
}
}
type Build = Box<dyn FnOnce(Size, f32) -> Box<dyn DeniseApp>>;
#[derive(Clone, Debug)]
pub struct Waker(EventLoopProxy<()>);
impl Waker {
pub fn wake(&self) {
let _ = self.0.send_event(());
}
}
const _: fn() = || {
fn shareable<T: Send + Sync>() {}
shareable::<Waker>();
};
fn boxed<A, B>(build: B) -> Build
where
A: DeniseApp + 'static,
B: FnOnce(Size, f32) -> A + 'static,
{
Box::new(move |size, scale| Box::new(build(size, scale)))
}
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct Readme;