denise-win32 0.31.0

Windows child-window control for Denise, for a panel inside an existing Win32 application.
Documentation
//! A Denise panel in a Win32 child window.
//!
//! The oldest of the reasons this project exists. CoreCanvas shipped inside
//! Windows applications that were not going to be rewritten — MFC, WinForms, VB6
//! through the ActiveX shim — and the thing they all need is a control they can
//! put in a dialog next to the ones they already have.
//!
//! So: [`DeniseControl`] registers a window class and creates a child `HWND`. The
//! host owns the window, the message loop and the parent; Denise owns the pixels
//! inside one rectangle and nothing else.
//!
//! # What is different from the bare-metal backends
//!
//! - **The host owns the message loop.** There is no `run` function here. Windows
//!   decides when to paint and Denise answers, which is the opposite of the DRM
//!   backend where Denise decides and the display follows.
//! - **Damage is real bandwidth.** `BitBlt` moves only the rectangles it is given,
//!   unlike a DRM page flip where the whole buffer goes regardless. The tree's
//!   damage is worth passing on rather than rounding up to the client area.
//! - **The pixel format already matches.** A 32-bit `BI_RGB` DIB section is
//!   `0xAARRGGBB` in a little-endian `DWORD`, which is exactly what the rasteriser
//!   writes. No conversion pass anywhere.
//! - **There is already a cursor.** Windows draws one, so the composited sprite
//!   stays off: `Ui::show_cursor(false)`, which since M5 is a decision that sticks
//!   rather than one the next mouse move overrides.
//!
//! # No console window
//!
//! A Rust binary defaults to the **console** subsystem, so Windows allocates a
//! console and it sits behind the app looking like a mistake. The `.exe` that
//! hosts the control — not this library — decides that, with a crate-level
//! attribute:
//!
//! ```ignore
//! #![windows_subsystem = "windows"]
//! ```
//!
//! The cost is that `stdout` and `stderr` go nowhere, including panic messages
//! and anything printed before a window exists. So the usual form keeps the
//! console while developing and drops it for the build that ships:
//!
//! ```ignore
//! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
//! ```
//!
//! It is ignored on non-Windows targets, so it needs no `cfg(windows)` of its
//! own. A host that wants both — no console, and somewhere for diagnostics to go
//! — should log to a file or `OutputDebugString` rather than to a stream nothing
//! is reading.
//!
//! # Testing
//!
//! [`keymap`] is platform-independent and its tests run everywhere. That is not
//! tidiness: it is a table of a hundred numbers, it is the part that breaks, and
//! discovering that on a CI runner rather than locally is a slow way to discover
//! it — which is exactly what happened the first time. Only the window and the
//! DIB section are gated to Windows, the same split `denise-drm` and
//! `denise-evdev` make.
//!
//! # Status
//!
//! Run on real hardware — Windows 11 ARM64, where Tab reaches the control and
//! AltGr and the dead keys compose — and built and tested by CI on every push.
//! Still unverified: focus behaviour inside a real *dialog*, and DPI changes.
//! For the latter, the toolkit's answer exists and is documented in
//! `docs/design.md`: the host rebuilds with `theme.scaled(factor)` (or
//! `denise_ui_new_scaled` over the C ABI) when `WM_DPICHANGED` arrives — what
//! is missing is a host that has actually done it.

// `keymap` is deliberately outside the gate: it is a table of a hundred numbers
// mapping `u16` to `KeyCode`, which is exactly the sort of thing that goes wrong
// and exactly the sort of thing that should not need a Windows machine to test.
// Only the window and the DIB section are platform code. The same split
// `denise-drm` and `denise-evdev` make, for the same reason.
pub mod keymap;

#[cfg(windows)]
mod control;
#[cfg(windows)]
mod surface;

pub use keymap::key_code;

#[cfg(windows)]
pub use control::{ControlDelegate, DeniseControl};
#[cfg(windows)]
pub use surface::DibSurface;

/// Failures from this backend.
#[cfg(windows)]
#[derive(Debug)]
pub enum Error {
    /// A surface was asked for with no pixels in it.
    EmptySurface,

    /// `CreateDIBSection` failed, or handed back no pixels.
    DibSection,

    /// `CreateCompatibleDC` failed.
    MemoryDc,

    /// The window class could not be registered.
    RegisterClass,

    /// `CreateWindowEx` failed.
    CreateWindow,

    /// A surface operation failed.
    Surface(denise::SurfaceError),
}

#[cfg(windows)]
impl core::fmt::Display for Error {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::EmptySurface => f.write_str("a surface needs a non-zero width and height"),
            Self::DibSection => f.write_str("could not create a DIB section"),
            Self::MemoryDc => f.write_str("could not create a memory device context"),
            Self::RegisterClass => f.write_str("could not register the window class"),
            Self::CreateWindow => f.write_str("could not create the control window"),
            Self::Surface(err) => core::fmt::Display::fmt(err, f),
        }
    }
}

#[cfg(windows)]
impl core::error::Error for Error {
    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
        match self {
            Self::Surface(err) => core::error::Error::source(err),
            _ => None,
        }
    }
}

#[cfg(windows)]
impl From<denise::SurfaceError> for Error {
    fn from(err: denise::SurfaceError) -> Self {
        Self::Surface(err)
    }
}

/// Compiles the examples in this crate's README, so they cannot drift from the API
/// they claim to demonstrate. Never built except under `cargo test --doc`.
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct Readme;