#[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;
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, thiserror::Error)]
pub enum Error {
#[error("event loop: {0}")]
EventLoop(#[from] winit::error::EventLoopError),
#[error("window creation: {0}")]
Window(#[from] winit::error::OsError),
#[cfg(not(target_os = "macos"))]
#[error("softbuffer: {0}")]
Softbuffer(#[from] softbuffer::SoftBufferError),
#[error(transparent)]
Surface(#[from] denise::SurfaceError),
#[error("present: {0}")]
Present(String),
#[error("gpu: {0}")]
Gpu(String),
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Present {
#[default]
Software,
Gpu,
}
#[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,
}
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,
}
}
}
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 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::new()?;
let mut runner = Runner::new(config, boxed(build));
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>>;
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;