Skip to main content

denise_macos/
lib.rs

1//! An embeddable Cocoa view backend for Denise.
2//!
3//! Not a way to ship Denise on a Mac — `denise-winit` already previews on one,
4//! and a desktop application should use a desktop toolkit. This exists for the
5//! same reason the Win32 control does: an existing Cocoa application that wants a
6//! Denise panel *inside* it, next to its own views, with the host owning the
7//! window and the run loop.
8//!
9//! ```no_run
10//! # #[cfg(target_os = "macos")]
11//! # fn demo() -> Result<(), denise_macos::Error> {
12//! use denise::Size;
13//! use denise_macos::ViewSurface;
14//!
15//! // The host has a view; Denise has a surface the size of its backing store.
16//! let mut surface = ViewSurface::new(Size::new(800, 480), 2.0)?;
17//! # let _ = &mut surface;
18//! # Ok(())
19//! # }
20//! ```
21//!
22//! # What is different from the bare-metal backends
23//!
24//! - **The host owns the run loop.** There is no `run` function here. AppKit
25//!   decides when to draw and Denise answers, which is the opposite of the DRM
26//!   backend where Denise decides and the display follows.
27//! - **Damage is real.** `setNeedsDisplayInRect:` genuinely limits what gets
28//!   composited, unlike a DRM page flip where the whole buffer goes regardless.
29//!   So the rectangles the tree produces are worth passing on rather than
30//!   rounding up to the whole view.
31//! - **Points are not pixels.** A Retina view is 2 physical pixels per point.
32//!   Denise lays out in physical pixels throughout — the conversion happens once,
33//!   at this edge, and nothing above it needs the scale factor to hit-test.
34//! - **There is already a cursor.** The host's window system draws one, so the
35//!   composited sprite must stay off: `Ui::show_cursor(false)`, which since M5 is
36//!   a decision that sticks rather than one the next mouse move overrides.
37
38#![cfg(target_os = "macos")]
39
40mod keymap;
41mod surface;
42mod view;
43
44pub use keymap::key_code;
45pub use surface::ViewSurface;
46pub use view::{DeniseView, ViewDelegate, ViewState};
47
48/// Failures from this backend.
49#[derive(Debug)]
50pub enum Error {
51    /// A surface was asked for with no pixels in it.
52    EmptySurface,
53
54    /// `CGColorSpaceCreateDeviceRGB` returned null, which should not happen and
55    /// leaves nothing sensible to fall back to.
56    ColorSpace,
57
58    /// `CGBitmapContextCreate` failed, or produced a pitch that is not a whole
59    /// number of 32-bit words.
60    BitmapContext,
61
62    /// A surface operation failed.
63    Surface(denise::SurfaceError),
64}
65
66impl core::fmt::Display for Error {
67    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
68        match self {
69            Self::EmptySurface => f.write_str("a surface needs a non-zero width and height"),
70            Self::ColorSpace => f.write_str("could not create a device RGB colour space"),
71            Self::BitmapContext => f.write_str("could not create a bitmap context"),
72            Self::Surface(err) => core::fmt::Display::fmt(err, f),
73        }
74    }
75}
76
77impl core::error::Error for Error {
78    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
79        match self {
80            Self::Surface(err) => core::error::Error::source(err),
81            _ => None,
82        }
83    }
84}
85
86impl From<denise::SurfaceError> for Error {
87    fn from(err: denise::SurfaceError) -> Self {
88        Self::Surface(err)
89    }
90}
91
92/// Compiles the examples in this crate's README, so they cannot drift from the API
93/// they claim to demonstrate. Never built except under `cargo test --doc`.
94#[cfg(doctest)]
95#[doc = include_str!("../README.md")]
96struct Readme;