use std::num::NonZeroU32;
use std::rc::Rc;
use std::sync::Arc;
use smudge_core::app::{App, AppConfig, FrameOutput, FrameStats, RepaintMode};
use smudge_core::clipboard::{Clipboard, MemoryClipboard};
use smudge_core::element::IntoElement;
use smudge_core::geometry::{Color, PixelRect, Rect, Size};
use smudge_core::window::{Cursor, WindowCommand};
use smudge_cpu::CpuRenderer;
use softbuffer::{Context, Surface};
use std::time::Duration;
use web_time::Instant;
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalPosition, LogicalSize, PhysicalSize};
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::raw_window_handle::{
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
};
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
use winit::window::WindowAttributes;
use winit::window::{CursorIcon, ResizeDirection, Window, WindowId};
#[cfg(not(target_arch = "wasm32"))]
use crate::clipboard::SystemClipboard;
use crate::error::PlatformError;
use crate::events::EventTranslator;
use crate::present::{
DamageHistory, PresentStats, PresentStatsHandle, copy_rect, damage_region, present_skipped,
tint_rects,
};
use crate::renderer::RendererChoice;
#[cfg(feature = "gpu")]
use smudge_gpu::AdapterSummary;
pub(crate) enum PlatformEvent {
Wake,
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
A11y(accesskit_winit::Event),
}
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
impl From<accesskit_winit::Event> for PlatformEvent {
fn from(event: accesskit_winit::Event) -> PlatformEvent {
PlatformEvent::A11y(event)
}
}
type UserEvent = PlatformEvent;
pub type FrameHook = Rc<dyn Fn(FrameStats, PresentStats)>;
pub type EventHook = Rc<dyn Fn(&mut App)>;
#[derive(Clone)]
pub struct WindowOptions {
pub title: String,
pub size: Size,
pub clear_color: Color,
pub repaint: RepaintMode,
pub show_damage: bool,
pub renderer: RendererChoice,
pub stats: Option<PresentStatsHandle>,
pub on_frame: Option<FrameHook>,
pub on_event: Option<EventHook>,
pub frame_limit: Option<u64>,
pub decorations: bool,
pub min_size: Option<Size>,
pub app_id: Option<String>,
}
impl Default for WindowOptions {
fn default() -> Self {
WindowOptions {
title: String::new(),
size: Size {
width: 1024.0,
height: 768.0,
},
clear_color: Color::WHITE,
repaint: RepaintMode::Damage,
show_damage: false,
renderer: RendererChoice::default(),
stats: None,
on_frame: None,
on_event: None,
frame_limit: None,
decorations: true,
min_size: None,
app_id: None,
}
}
}
impl std::fmt::Debug for WindowOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WindowOptions")
.field("title", &self.title)
.field("size", &self.size)
.field("clear_color", &self.clear_color)
.field("repaint", &self.repaint)
.field("show_damage", &self.show_damage)
.field("renderer", &self.renderer)
.field("stats", &self.stats)
.field("on_frame", &self.on_frame.is_some())
.field("on_event", &self.on_event.is_some())
.field("frame_limit", &self.frame_limit)
.field("decorations", &self.decorations)
.field("min_size", &self.min_size)
.field("app_id", &self.app_id)
.finish()
}
}
pub fn run<E: IntoElement + 'static>(
options: WindowOptions,
build: impl FnOnce() -> E + 'static,
) -> Result<(), PlatformError> {
run_with(options, build, |_| {})
}
pub fn run_with<E: IntoElement + 'static>(
options: WindowOptions,
build: impl FnOnce() -> E + 'static,
configure: impl FnOnce(&mut App) + 'static,
) -> Result<(), PlatformError> {
if !options.clear_color.is_opaque() {
return Err(PlatformError::ClearColor);
}
let renderer = options.renderer.with_env_override()?;
let event_loop = EventLoop::<UserEvent>::with_user_event()
.build()
.map_err(PlatformError::EventLoop)?;
let proxy = event_loop.create_proxy();
#[cfg_attr(target_arch = "wasm32", allow(unused_mut))]
let mut platform = Platform {
renderer,
options,
build: Some(build),
configure: Some(Box::new(configure)),
state: None,
error: None,
proxy,
};
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::EventLoopExtWebSys;
event_loop.spawn_app(platform);
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
{
event_loop
.run_app(&mut platform)
.map_err(PlatformError::EventLoop)?;
match platform.error.take() {
Some(error) => Err(error),
None => Ok(()),
}
}
}
struct Platform<E, B>
where
E: IntoElement,
B: FnOnce() -> E,
{
renderer: RendererChoice,
options: WindowOptions,
build: Option<B>,
configure: Option<Configure>,
state: Option<WindowState>,
error: Option<PlatformError>,
proxy: winit::event_loop::EventLoopProxy<UserEvent>,
}
type Configure = Box<dyn FnOnce(&mut App)>;
struct WindowState {
window: Arc<Window>,
app: App,
translator: EventTranslator,
presenter: Presenter,
stats: PresentStats,
stats_handle: Option<PresentStatsHandle>,
on_frame: Option<FrameHook>,
on_event: Option<EventHook>,
rendered: bool,
frame_limit: Option<u64>,
frames_presented: u64,
frame_period: Option<Duration>,
next_frame: Option<Instant>,
scale: f32,
physical: (u32, u32),
ime_area: Option<Rect>,
ime_scale: f32,
ime_allowed: bool,
cursor: CursorIcon,
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
adapter: accesskit_winit::Adapter,
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
a11y: smudge_a11y::Projection,
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
a11y_active: bool,
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
a11y_full: bool,
}
#[derive(Default)]
struct PendingFrame {
damage: Vec<PixelRect>,
full: bool,
}
impl<E, B> Platform<E, B>
where
E: IntoElement + 'static,
B: FnOnce() -> E + 'static,
{
fn create(&mut self, event_loop: &ActiveEventLoop) -> Result<WindowState, PlatformError> {
let attributes = Window::default_attributes()
.with_title(self.options.title.clone())
.with_inner_size(LogicalSize::new(
f64::from(self.options.size.width),
f64::from(self.options.size.height),
))
.with_decorations(self.options.decorations)
.with_visible(false);
let attributes = match self.options.min_size {
Some(size) => attributes.with_min_inner_size(LogicalSize::new(
f64::from(size.width),
f64::from(size.height),
)),
None => attributes,
};
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
let attributes = match self.options.app_id.clone() {
Some(app_id) => with_app_name(attributes, app_id),
None => attributes,
};
#[cfg(target_arch = "wasm32")]
let attributes = {
use winit::platform::web::WindowAttributesExtWebSys;
attributes.with_append(true)
};
let window = Arc::new(
event_loop
.create_window(attributes)
.map_err(PlatformError::Window)?,
);
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
let adapter = accesskit_winit::Adapter::with_event_loop_proxy(
event_loop,
&window,
self.proxy.clone(),
);
window.set_visible(true);
let scale = window.scale_factor() as f32;
let physical = window.inner_size();
let build = self
.build
.take()
.expect("the window is created only once, so its element is still waiting");
let mut app = App::new(
AppConfig {
viewport: logical_size(physical, scale),
scale,
clear_color: self.options.clear_color,
repaint: self.options.repaint,
},
build,
);
if let Some(configure) = self.configure.take() {
configure(&mut app);
}
let proxy = self.proxy.clone();
app.set_remote_waker(move || {
let _ = proxy.send_event(PlatformEvent::Wake);
});
let (width, height) = app.surface_size();
let presenter = create_presenter(
&window,
self.renderer,
width,
height,
self.options.show_damage,
)?;
#[cfg(feature = "gpu")]
if let (Some(handle), Some(summary)) =
(self.options.stats.as_ref(), presenter.adapter_summary())
{
handle.set_adapter(summary);
}
let weak = Arc::downgrade(&window);
app.set_wake_callback(move || {
if let Some(window) = weak.upgrade() {
window.request_redraw();
}
});
window.request_redraw();
#[cfg(not(target_arch = "wasm32"))]
let clipboard: Box<dyn Clipboard> = match SystemClipboard::new() {
Ok(clipboard) => Box::new(clipboard),
Err(_) => Box::new(MemoryClipboard::default()),
};
#[cfg(target_arch = "wasm32")]
let clipboard: Box<dyn Clipboard> = Box::new(MemoryClipboard::default());
app.set_clipboard(clipboard);
let frame_period = frame_period(&window);
Ok(WindowState {
window,
app,
translator: EventTranslator::new(),
presenter,
stats: PresentStats::default(),
stats_handle: self.options.stats.clone(),
on_frame: self.options.on_frame.take(),
on_event: self.options.on_event.take(),
rendered: false,
frame_limit: self.options.frame_limit,
frames_presented: 0,
frame_period,
next_frame: None,
scale,
physical: (physical.width, physical.height),
ime_area: None,
ime_scale: scale,
ime_allowed: false,
cursor: CursorIcon::Default,
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
adapter,
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
a11y: smudge_a11y::Projection::new(self.options.title.clone()),
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
a11y_active: false,
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
a11y_full: false,
})
}
fn resize(&mut self, physical: PhysicalSize<u32>, scale: f64) -> Result<(), PlatformError> {
let Some(state) = self.state.as_mut() else {
return Ok(());
};
state.report_maximized();
let scale = scale as f32;
if !scale.is_finite() || scale <= 0.0 {
return Ok(());
}
if physical.width == 0 || physical.height == 0 {
return Ok(());
}
if state.scale == scale && state.physical == (physical.width, physical.height) {
return Ok(());
}
let (Some(width), Some(height)) = (
NonZeroU32::new(physical.width),
NonZeroU32::new(physical.height),
) else {
return Ok(());
};
state.presenter.resize(width, height)?;
state.app.set_viewport(logical_size(physical, scale), scale);
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
if state.scale != scale {
state.a11y_full = true;
}
debug_assert_eq!(
state.app.surface_size(),
(width.get(), height.get()),
"the app's surface size must match the resized surface and renderer"
);
state.scale = scale;
state.physical = (physical.width, physical.height);
state.window.request_redraw();
Ok(())
}
fn redraw(&mut self) -> Result<(), PlatformError> {
let Some(state) = self.state.as_mut() else {
return Ok(());
};
if state.rendered
&& let Some(on_event) = &state.on_event
{
(on_event)(&mut state.app);
}
let driven = state.app.has_tick() || state.on_event.is_some();
let started = Instant::now();
state.next_frame = None;
let output = state.app.frame();
state.rendered = true;
let presented = state.presenter.render_and_present(&output, driven)?;
if let Some(stats) = presented {
state.stats = stats;
if let Some(handle) = &state.stats_handle {
handle.store(stats);
}
state.frames_presented += 1;
if let Some(on_frame) = &state.on_frame {
on_frame(state.app.stats().clone(), stats);
}
}
state.update_ime();
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
state.update_a11y();
if presented.is_some() && state.below_frame_limit() {
state.window.request_redraw();
} else if driven {
match state.frame_period {
Some(period) => match next_frame_at(started, period, Instant::now()) {
Some(at) => state.next_frame = Some(at),
None => state.window.request_redraw(),
},
None => state.window.request_redraw(),
}
}
Ok(())
}
fn wait_for_timers(&mut self, event_loop: &ActiveEventLoop) {
let Some(state) = self.state.as_mut() else {
return;
};
if state.app.has_tick() || state.on_event.is_some() {
if let Some(at) = state.next_frame {
if at <= Instant::now() {
state.next_frame = None;
state.window.request_redraw();
} else {
event_loop.set_control_flow(ControlFlow::WaitUntil(at));
}
}
return;
}
if wait_decision(state.app.next_deadline(), Instant::now()) == WaitDecision::Now {
state.app.run_due_timers();
if state.app.needs_frame() {
state.window.request_redraw();
}
}
match wait_decision(state.app.next_deadline(), Instant::now()) {
WaitDecision::Now => state.window.request_redraw(),
WaitDecision::Until(deadline) => {
event_loop.set_control_flow(ControlFlow::WaitUntil(deadline));
}
WaitDecision::Wait => event_loop.set_control_flow(ControlFlow::Wait),
}
}
fn fail(&mut self, event_loop: &ActiveEventLoop, error: PlatformError) {
if self.error.is_none() {
self.error = Some(error);
}
event_loop.exit();
}
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
fn handle_a11y_event(&mut self, event: accesskit_winit::Event) {
let Some(state) = self.state.as_mut() else {
return;
};
match event.window_event {
accesskit_winit::WindowEvent::InitialTreeRequested => {
state.a11y_active = true;
state.a11y_full = true;
if state.app.needs_frame() {
state.window.request_redraw();
} else {
state.update_a11y();
}
}
accesskit_winit::WindowEvent::ActionRequested(request) => {
let id = smudge_core::tree::WidgetId::from_u64(request.target_node.0);
match request.action {
accesskit::Action::Click => {
state.app.press(id);
}
accesskit::Action::Focus => {
state.app.request_focus(id);
}
_ => {}
}
if state.app.needs_frame() {
state.window.request_redraw();
}
}
accesskit_winit::WindowEvent::AccessibilityDeactivated => {
state.a11y_active = false;
}
}
}
}
impl<E, B> ApplicationHandler<UserEvent> for Platform<E, B>
where
E: IntoElement + 'static,
B: FnOnce() -> E + 'static,
{
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.state.is_some() {
return;
}
match self.create(event_loop) {
Ok(state) => self.state = Some(state),
Err(error) => self.fail(event_loop, error),
}
}
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
match event {
PlatformEvent::Wake => {
if let Some(state) = self.state.as_mut()
&& state.app.needs_frame()
{
state.window.request_redraw();
}
}
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
PlatformEvent::A11y(event) => self.handle_a11y_event(event),
}
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
if let Some(state) = self.state.as_mut() {
state.adapter.process_event(&state.window, &event);
}
let result = match event {
WindowEvent::CloseRequested => {
event_loop.exit();
Ok(())
}
WindowEvent::Resized(physical) => {
let scale = self
.state
.as_ref()
.map_or(1.0, |state| state.window.scale_factor());
self.resize(physical, scale)
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
match self.state.as_ref().map(|state| state.window.inner_size()) {
Some(physical) => self.resize(physical, scale_factor),
None => Ok(()),
}
}
WindowEvent::RedrawRequested => {
let result = self.redraw();
if result.is_ok() && self.state.as_ref().is_some_and(WindowState::finished) {
event_loop.exit();
}
result
}
other => {
if let Some(state) = self.state.as_mut() {
let input = state.translator.translate(&other, state.scale);
if let Some(input) = input {
state.app.handle_event(input);
if state.app.needs_frame() {
state.window.request_redraw();
}
}
state.update_ime();
}
Ok(())
}
};
if let Some(state) = self.state.as_mut() {
state.apply_window_commands(event_loop);
state.update_cursor();
}
if let Err(error) = result {
self.fail(event_loop, error);
}
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
self.wait_for_timers(event_loop);
if let Some(state) = self.state.as_mut() {
state.apply_window_commands(event_loop);
state.update_cursor();
}
}
}
impl WindowState {
fn apply_window_commands(&mut self, event_loop: &ActiveEventLoop) {
for command in self.app.take_window_commands() {
match command {
WindowCommand::Move => {
if self.window.drag_window().is_ok() {
self.app.release_press();
}
}
WindowCommand::Resize(edge) => {
if self
.window
.drag_resize_window(resize_direction(edge))
.is_ok()
{
self.app.release_press();
}
}
WindowCommand::ToggleMaximize => {
let maximized = !self.window.is_maximized();
self.window.set_maximized(maximized);
self.report_maximized();
}
WindowCommand::Close => {
if cfg!(not(target_arch = "wasm32")) {
event_loop.exit();
}
}
_ => {}
}
}
}
fn report_maximized(&mut self) {
let maximized = self.window.is_maximized();
self.app.set_window_state(
smudge_core::window::WindowState::default().with_maximized(maximized),
);
}
fn update_cursor(&mut self) {
let icon = cursor_icon(self.app.cursor());
if icon != self.cursor {
self.cursor = icon;
self.window.set_cursor(icon);
}
}
fn update_ime(&mut self) {
let area = self.app.ime_area();
if area == self.ime_area && self.scale == self.ime_scale {
return;
}
self.ime_area = area;
self.ime_scale = self.scale;
match area {
Some(area) => {
if !self.ime_allowed {
self.ime_allowed = true;
self.window.set_ime_allowed(true);
}
self.window.set_ime_cursor_area(
LogicalPosition::new(f64::from(area.x0), f64::from(area.y0)),
LogicalSize::new(f64::from(area.width()), f64::from(area.height())),
);
}
None => {
if self.ime_allowed {
self.ime_allowed = false;
self.window.set_ime_allowed(false);
}
}
}
}
#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
fn update_a11y(&mut self) {
if !self.a11y_active || !(self.a11y_full || self.app.has_a11y_changes()) {
return;
}
let full = std::mem::take(&mut self.a11y_full);
let (width, height) = self.physical;
let (projection, app, scale) = (&mut self.a11y, &mut self.app, self.scale);
self.adapter.update_if_active(|| {
projection.update(app, f64::from(width), f64::from(height), scale, full)
});
}
fn finished(&self) -> bool {
self.frame_limit
.is_some_and(|limit| self.frames_presented >= limit)
}
fn below_frame_limit(&self) -> bool {
self.frame_limit
.is_some_and(|limit| self.frames_presented < limit)
}
}
fn create_presenter(
window: &Arc<Window>,
choice: RendererChoice,
width: u32,
height: u32,
show_damage: bool,
) -> Result<Presenter, PlatformError> {
#[cfg(not(feature = "gpu"))]
{
match choice {
RendererChoice::Gpu => Err(PlatformError::Renderer(String::from(
"the GPU renderer needs a build with the gpu feature",
))),
_ => Ok(Presenter::Cpu(Box::new(CpuPresenter::new(
window,
width,
height,
show_damage,
)?))),
}
}
#[cfg(feature = "gpu")]
match choice {
RendererChoice::Cpu => Ok(Presenter::Cpu(Box::new(CpuPresenter::new(
window,
width,
height,
show_damage,
)?))),
RendererChoice::Auto if show_damage => Ok(Presenter::Cpu(Box::new(CpuPresenter::new(
window,
width,
height,
show_damage,
)?))),
RendererChoice::Gpu | RendererChoice::Auto => {
let explicit = choice == RendererChoice::Gpu;
if explicit && show_damage {
return Err(PlatformError::Renderer(String::from(
"the damage overlay tints the window buffer of the CPU \
presentation path and cannot run on the GPU renderer",
)));
}
match crate::gpu::GpuPath::new(window, width, height, explicit)? {
Some(gpu) => Ok(Presenter::Gpu(Box::new(gpu))),
None => Ok(Presenter::Cpu(Box::new(CpuPresenter::new(
window,
width,
height,
show_damage,
)?))),
}
}
}
}
enum Presenter {
Cpu(Box<CpuPresenter>),
#[cfg(feature = "gpu")]
Gpu(Box<crate::gpu::GpuPath>),
}
impl Presenter {
fn render_and_present(
&mut self,
frame: &FrameOutput<'_>,
has_tick: bool,
) -> Result<Option<PresentStats>, PlatformError> {
match self {
Presenter::Cpu(cpu) => cpu.render_and_present(frame, has_tick),
#[cfg(feature = "gpu")]
Presenter::Gpu(gpu) => gpu.render_and_present(frame, has_tick),
}
}
fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), PlatformError> {
match self {
Presenter::Cpu(cpu) => cpu.resize(width, height),
#[cfg(feature = "gpu")]
Presenter::Gpu(gpu) => gpu.resize(width.get(), height.get()),
}
}
#[cfg(feature = "gpu")]
fn adapter_summary(&self) -> Option<AdapterSummary> {
match self {
Presenter::Cpu(_) => None,
Presenter::Gpu(gpu) => Some(gpu.adapter_summary()),
}
}
}
struct CpuPresenter {
surface: Surface<SharedWindow, SharedWindow>,
window: SharedWindow,
renderer: CpuRenderer,
show_damage: bool,
history: DamageHistory,
pending: PendingFrame,
region: Vec<PixelRect>,
reported: Vec<softbuffer::Rect>,
}
impl CpuPresenter {
fn new(
window: &Arc<Window>,
width: u32,
height: u32,
show_damage: bool,
) -> Result<CpuPresenter, PlatformError> {
let shared = SharedWindow(Arc::clone(window));
let context = Context::new(shared.clone()).map_err(PlatformError::Surface)?;
let mut surface = Surface::new(&context, shared).map_err(PlatformError::Surface)?;
drop(context);
if let (Some(width), Some(height)) = (NonZeroU32::new(width), NonZeroU32::new(height)) {
surface
.resize(width, height)
.map_err(PlatformError::Surface)?;
}
Ok(CpuPresenter {
surface,
window: SharedWindow(Arc::clone(window)),
renderer: CpuRenderer::new(width, height),
show_damage,
history: DamageHistory::default(),
pending: PendingFrame::default(),
region: Vec::new(),
reported: Vec::new(),
})
}
fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), PlatformError> {
self.surface
.resize(width, height)
.map_err(PlatformError::Surface)?;
self.renderer.resize(width.get(), height.get());
Ok(())
}
fn render_and_present(
&mut self,
frame: &FrameOutput<'_>,
has_tick: bool,
) -> Result<Option<PresentStats>, PlatformError> {
let (width, height) = (self.renderer.width(), self.renderer.height());
if width == 0 || height == 0 {
return Ok(None);
}
let raster_start = Instant::now();
self.renderer.render(frame);
let raster = raster_start.elapsed();
self.pending.damage.clear();
self.pending.damage.extend_from_slice(frame.damage);
self.pending.full = frame.full;
if present_skipped(&self.pending.damage, has_tick, self.show_damage) {
return Ok(None);
}
let acquire_start = Instant::now();
let mut buffer = self.surface.buffer_mut().map_err(PlatformError::Surface)?;
let acquire = acquire_start.elapsed();
let present_start = Instant::now();
debug_assert_eq!(
buffer.len(),
self.renderer.pixels().len() / 4,
"the window buffer must hold one entry per rendered pixel"
);
let age = buffer.age();
let with_damage = !self.show_damage
&& damage_region(
age,
&self.pending.damage,
self.pending.full,
&self.history,
&mut self.region,
);
let mut bytes_copied = 0;
if with_damage {
self.reported.clear();
for rect in &self.region {
let (Some(rect_width), Some(rect_height)) =
(NonZeroU32::new(rect.width), NonZeroU32::new(rect.height))
else {
continue;
};
bytes_copied += u64::from(rect.width) * u64::from(rect.height) * 4;
self.reported.push(softbuffer::Rect {
x: rect.x,
y: rect.y,
width: rect_width,
height: rect_height,
});
}
for &rect in &self.region {
copy_rect(self.renderer.pixels(), width, &mut buffer, rect);
}
self.window.0.pre_present_notify();
buffer
.present_with_damage(&self.reported)
.map_err(PlatformError::Surface)?;
} else {
bytes_copied = u64::from(width) * u64::from(height) * 4;
copy_rect(
self.renderer.pixels(),
width,
&mut buffer,
PixelRect {
x: 0,
y: 0,
width,
height,
},
);
if self.show_damage {
tint_rects(&mut buffer, width, &self.pending.damage);
}
self.window.0.pre_present_notify();
buffer.present().map_err(PlatformError::Surface)?;
}
self.history.push(&self.pending.damage, self.pending.full);
Ok(Some(PresentStats {
buffer_age: age,
with_damage,
bytes_copied,
surface_width: width,
surface_height: height,
raster,
acquire,
present: present_start.elapsed(),
renderer: "cpu",
}))
}
}
fn logical_size(physical: PhysicalSize<u32>, scale: f32) -> Size {
Size {
width: physical.width as f32 / scale,
height: physical.height as f32 / scale,
}
}
fn cursor_icon(cursor: Cursor) -> CursorIcon {
match cursor {
Cursor::Default => CursorIcon::Default,
Cursor::Pointer => CursorIcon::Pointer,
Cursor::Text => CursorIcon::Text,
Cursor::Resize(edge) => CursorIcon::from(resize_direction(edge)),
_ => CursorIcon::Default,
}
}
fn resize_direction(edge: smudge_core::window::ResizeEdge) -> ResizeDirection {
use smudge_core::window::ResizeEdge;
match edge {
ResizeEdge::North => ResizeDirection::North,
ResizeEdge::South => ResizeDirection::South,
ResizeEdge::East => ResizeDirection::East,
ResizeEdge::West => ResizeDirection::West,
ResizeEdge::NorthEast => ResizeDirection::NorthEast,
ResizeEdge::NorthWest => ResizeDirection::NorthWest,
ResizeEdge::SouthEast => ResizeDirection::SouthEast,
ResizeEdge::SouthWest => ResizeDirection::SouthWest,
}
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
fn with_app_name(attributes: WindowAttributes, app_id: String) -> WindowAttributes {
let attributes = {
use winit::platform::wayland::WindowAttributesExtWayland as _;
attributes.with_name(app_id.clone(), app_id.clone())
};
{
use winit::platform::x11::WindowAttributesExtX11 as _;
attributes.with_name(app_id.clone(), app_id)
}
}
fn frame_period(window: &Window) -> Option<Duration> {
if cfg!(target_arch = "wasm32") {
return None;
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
{
use winit::platform::wayland::WindowExtWayland;
if window.xdg_toplevel().is_some() {
return None;
}
}
let millihertz = window
.current_monitor()
.and_then(|monitor| monitor.refresh_rate_millihertz())
.filter(|millihertz| *millihertz > 0)
.unwrap_or(60_000);
Some(Duration::from_secs_f64(1_000.0 / f64::from(millihertz)))
}
fn next_frame_at(started: Instant, period: Duration, now: Instant) -> Option<Instant> {
let at = started + period;
(at > now).then_some(at)
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum WaitDecision {
Now,
Until(Instant),
Wait,
}
fn wait_decision(deadline: Option<Instant>, now: Instant) -> WaitDecision {
match deadline {
Some(deadline) if deadline <= now => WaitDecision::Now,
Some(deadline) => WaitDecision::Until(deadline),
None => WaitDecision::Wait,
}
}
#[derive(Clone)]
pub(crate) struct SharedWindow(pub(crate) Arc<Window>);
impl HasWindowHandle for SharedWindow {
fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
self.0.window_handle()
}
}
impl HasDisplayHandle for SharedWindow {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
self.0.display_handle()
}
}
#[cfg(test)]
#[path = "window_tests.rs"]
mod tests;