cranpose 0.1.164

Cranpose runtime and UI facade
Documentation
use std::rc::Rc;

use cranpose_app_shell::{RootId, SurfaceMut};
use cranpose_render_wgpu::WgpuRenderer;
use cranpose_ui::{Point, Size, WindowRootDescriptor};

use crate::{
    embed::{EmbedError, SurfaceSize},
    embed_frame::{FrameTarget, changed_rect},
    embed_overlay::HostOverlayRoot,
    embed_protocol::{
        FrameUpdate, SurfaceId, WINDOW_ALWAYS_ON_TOP, WINDOW_DECORATED, WINDOW_RESIZABLE,
        WINDOW_SHADOW, WINDOW_TAKES_FOCUS, WINDOW_TRANSPARENT, WindowSpec,
    },
    native_window::{
        NativeWindowEvents, NativeWindowOptions, NativeWindowPositionOrigin, NativeWindowRequest,
        NativeWindowRootHandle, WindowFocus, WindowState,
    },
};

pub(crate) const MAX_FRAMES_IN_FLIGHT: u32 = 2;

pub(crate) struct WindowSurface {
    pub(crate) revision: u64,
    pub(crate) spec: WindowSpec,
    pub(crate) events: NativeWindowEvents,
    pub(crate) state: Option<WindowState>,
    pub(crate) root: NativeWindowRootHandle,
    pub(crate) host_position: Option<(f32, f32)>,
}

impl WindowSurface {
    pub(crate) fn from_request(surface: SurfaceId, request: &NativeWindowRequest) -> Self {
        Self {
            revision: request.revision,
            spec: window_spec(surface, &request.options),
            events: request.events.clone(),
            state: request.state,
            root: Rc::clone(&request.root),
            host_position: None,
        }
    }

    pub(crate) fn content_size(&self) -> Size {
        Size::new(self.spec.width, self.spec.height)
    }

    pub(crate) fn set_content_size(&self, size: Size) {
        self.root.set_size(size);
        if let Some(state) = self.state {
            state.set_size(size);
            state.set_frame_size(size);
        }
    }

    pub(crate) fn set_presented(&self, presented: bool) {
        if let Some(state) = self.state {
            state.set_presented(presented);
        }
    }

    pub(crate) fn host_moved(&mut self, x: f32, y: f32) {
        self.host_position = Some((x, y));
        if let Some(state) = self.state {
            state.set_position(Some(Point { x, y }));
        }
        if let Some(on_moved) = &self.events.on_moved {
            on_moved(x, y);
        }
    }

    pub(crate) fn host_resized(&self, size: Size) {
        self.set_content_size(size);
        if let Some(on_resized) = &self.events.on_resized {
            on_resized(size.width, size.height);
        }
    }

    pub(crate) fn close_requested(&self) {
        if let Some(on_close_requested) = &self.events.on_close_requested {
            on_close_requested();
        }
    }

    pub(crate) fn refresh(&mut self, request: &NativeWindowRequest) -> Option<WindowSpec> {
        if request.revision == self.revision {
            return None;
        }
        self.revision = request.revision;
        self.events = request.events.clone();
        self.state = request.state;
        let mut spec = window_spec(self.spec.surface, &request.options);
        if spec == self.spec {
            return None;
        }
        self.spec = spec.clone();
        if spec.position == self.host_position {
            spec.position = None;
        }
        Some(spec)
    }
}

pub(crate) fn window_spec(surface: SurfaceId, options: &NativeWindowOptions) -> WindowSpec {
    let flags = [
        (options.decorations, WINDOW_DECORATED),
        (options.transparent, WINDOW_TRANSPARENT),
        (options.resizable, WINDOW_RESIZABLE),
        (options.always_on_top, WINDOW_ALWAYS_ON_TOP),
        (options.shadow, WINDOW_SHADOW),
        (options.focus != WindowFocus::Never, WINDOW_TAKES_FOCUS),
    ]
    .into_iter()
    .filter(|(on, _)| *on)
    .fold(0, |flags, (_, bit)| flags | bit);
    WindowSpec {
        surface,
        title: options.title.clone(),
        position: options.x.zip(options.y),
        relative_to_host: options.position_origin == NativeWindowPositionOrigin::HostWindow,
        width: options.width,
        height: options.height,
        flags,
    }
}

pub(crate) enum SurfaceKind {
    Primary,
    Window(WindowSurface),
    Overlay(Rc<dyn WindowRootDescriptor>),
}

impl SurfaceKind {
    pub(crate) fn transparent(&self) -> bool {
        match self {
            Self::Primary => false,
            Self::Window(window) => window.spec.flags & WINDOW_TRANSPARENT != 0,
            Self::Overlay(_) => true,
        }
    }

    pub(crate) fn set_content_size(&self, size: Size) {
        match self {
            Self::Primary => {}
            Self::Window(window) => window.host_resized(size),
            Self::Overlay(descriptor) => {
                if let Some(overlay) = descriptor.as_any().downcast_ref::<HostOverlayRoot>() {
                    overlay.set_size(size);
                }
            }
        }
    }
}

pub(crate) struct SurfaceSlot {
    pub(crate) id: SurfaceId,
    pub(crate) root: RootId,
    pub(crate) kind: SurfaceKind,
    target: FrameTarget,
    size: SurfaceSize,
    drawn: Vec<u8>,
    shown: Vec<u8>,
    dirty: bool,
    visible: bool,
    next_frame_id: u32,
    in_flight: u32,
}

impl SurfaceSlot {
    pub(crate) fn new(
        id: SurfaceId,
        root: RootId,
        kind: SurfaceKind,
        device: &wgpu::Device,
        size: SurfaceSize,
    ) -> Self {
        Self {
            id,
            root,
            kind,
            target: FrameTarget::new(device, size.width, size.height),
            size,
            drawn: Vec::new(),
            shown: Vec::new(),
            dirty: true,
            visible: true,
            next_frame_id: 1,
            in_flight: 0,
        }
    }

    pub(crate) fn set_visible(&mut self, visible: bool) {
        if visible && !self.visible {
            self.dirty = true;
        }
        self.visible = visible;
    }

    pub(crate) fn mark_dirty(&mut self) {
        self.dirty = true;
    }

    pub(crate) fn wants_frame(&self) -> bool {
        self.visible && self.dirty
    }

    pub(crate) fn has_room(&self) -> bool {
        self.visible && self.in_flight < MAX_FRAMES_IN_FLIGHT
    }

    pub(crate) fn acknowledged(&mut self) {
        self.in_flight = self.in_flight.saturating_sub(1);
    }

    pub(crate) fn resize(&mut self, device: &wgpu::Device, size: SurfaceSize) -> bool {
        if size == self.size {
            return false;
        }
        if (size.width, size.height) != self.target.size() {
            self.target = FrameTarget::new(device, size.width, size.height);
            self.shown.clear();
        }
        self.size = size;
        self.dirty = true;
        true
    }

    pub(crate) fn produce(
        &mut self,
        surface: &mut SurfaceMut<'_, WgpuRenderer>,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
    ) -> Result<Option<FrameUpdate<'_>>, EmbedError> {
        let owed = surface.take_frame_owed();
        if !(self.dirty || owed || surface.needs_redraw()) {
            return Ok(None);
        }
        let (width, height) = self.target.size();
        surface
            .renderer()
            .render(self.target.texture(), self.target.view(), width, height)
            .map_err(|error| EmbedError::Render(format!("{error:?}")))?;
        self.target.read_into(device, queue, &mut self.drawn)?;
        self.dirty = false;
        let Some(rect) = changed_rect(&self.shown, &self.drawn, width, height) else {
            return Ok(None);
        };
        std::mem::swap(&mut self.shown, &mut self.drawn);
        let frame_id = self.next_frame_id;
        self.next_frame_id = self.next_frame_id.wrapping_add(1);
        self.in_flight += 1;
        Ok(Some(FrameUpdate {
            surface: self.id,
            frame_id,
            buffer_width: width,
            buffer_height: height,
            rect,
            pixels: &self.shown,
        }))
    }
}

#[cfg(test)]
#[path = "tests/embed_surfaces_tests.rs"]
mod tests;