linux: basic window, display, and atlas

This commit is contained in:
Dzmitry Malyshau 2024-01-26 23:51:35 -08:00
parent b0376aaf8f
commit e95bf24a1f
8 changed files with 582 additions and 84 deletions

View file

@ -26,7 +26,7 @@ anyhow.workspace = true
async-task = "4.7" async-task = "4.7"
backtrace = { version = "0.3", optional = true } backtrace = { version = "0.3", optional = true }
bitflags = "2.4.0" bitflags = "2.4.0"
blade-graphics = "0.3" blade = { package = "blade-graphics", version = "0.3" }
blade-macros = "0.2" blade-macros = "0.2"
collections = { path = "../collections" } collections = { path = "../collections" }
ctor.workspace = true ctor.workspace = true

View file

@ -1,7 +1,16 @@
mod blade_atlas;
mod blade_belt;
mod dispatcher; mod dispatcher;
mod display;
mod platform; mod platform;
mod text_system; mod text_system;
mod window;
pub(crate) use blade_atlas::*;
pub(crate) use dispatcher::*; pub(crate) use dispatcher::*;
pub(crate) use display::*;
pub(crate) use platform::*; pub(crate) use platform::*;
pub(crate) use text_system::*; pub(crate) use text_system::*;
pub(crate) use window::*;
use blade_belt::*;

View file

@ -0,0 +1,258 @@
use super::{BladeBelt, BladeBeltDescriptor};
use crate::{
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
Point, Size,
};
use anyhow::Result;
use collections::FxHashMap;
use derive_more::{Deref, DerefMut};
use etagere::BucketedAtlasAllocator;
use parking_lot::Mutex;
use std::{borrow::Cow, sync::Arc};
pub(crate) struct BladeAtlas(Mutex<BladeAtlasState>);
struct BladeAtlasState {
gpu: Arc<blade::Context>,
gpu_encoder: blade::CommandEncoder,
upload_belt: BladeBelt,
monochrome_textures: Vec<BladeAtlasTexture>,
polychrome_textures: Vec<BladeAtlasTexture>,
path_textures: Vec<BladeAtlasTexture>,
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
}
impl BladeAtlas {
pub(crate) fn new(gpu: &Arc<blade::Context>) -> Self {
BladeAtlas(Mutex::new(BladeAtlasState {
gpu: Arc::clone(gpu),
gpu_encoder: gpu.create_command_encoder(blade::CommandEncoderDesc {
name: "atlas",
buffer_count: 3,
}),
upload_belt: BladeBelt::new(BladeBeltDescriptor {
memory: blade::Memory::Upload,
min_chunk_size: 0x10000,
}),
monochrome_textures: Default::default(),
polychrome_textures: Default::default(),
path_textures: Default::default(),
tiles_by_key: Default::default(),
}))
}
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
let mut lock = self.0.lock();
let textures = match texture_kind {
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
AtlasTextureKind::Path => &mut lock.path_textures,
};
for texture in textures {
texture.clear();
}
}
pub fn start_frame(&self) {
let mut lock = self.0.lock();
lock.gpu_encoder.start();
}
pub fn finish_frame(&self) -> blade::SyncPoint {
let mut lock = self.0.lock();
let gpu = lock.gpu.clone();
let sync_point = gpu.submit(&mut lock.gpu_encoder);
lock.upload_belt.flush(&sync_point);
sync_point
}
}
impl PlatformAtlas for BladeAtlas {
fn get_or_insert_with<'a>(
&self,
key: &AtlasKey,
build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
) -> Result<AtlasTile> {
let mut lock = self.0.lock();
if let Some(tile) = lock.tiles_by_key.get(key) {
Ok(tile.clone())
} else {
let (size, bytes) = build()?;
let tile = lock.allocate(size, key.texture_kind());
lock.upload_texture(tile.texture_id, tile.bounds, &bytes);
lock.tiles_by_key.insert(key.clone(), tile.clone());
Ok(tile)
}
}
}
impl BladeAtlasState {
fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
let textures = match texture_kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
AtlasTextureKind::Path => &mut self.path_textures,
};
textures
.iter_mut()
.rev()
.find_map(|texture| texture.allocate(size))
.unwrap_or_else(|| {
let texture = self.push_texture(size, texture_kind);
texture.allocate(size).unwrap()
})
}
fn push_texture(
&mut self,
min_size: Size<DevicePixels>,
kind: AtlasTextureKind,
) -> &mut BladeAtlasTexture {
const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
width: DevicePixels(1024),
height: DevicePixels(1024),
};
let size = min_size.max(&DEFAULT_ATLAS_SIZE);
let format;
let usage;
match kind {
AtlasTextureKind::Monochrome => {
format = blade::TextureFormat::R8Unorm;
usage = blade::TextureUsage::COPY | blade::TextureUsage::RESOURCE;
}
AtlasTextureKind::Polychrome => {
format = blade::TextureFormat::Bgra8Unorm;
usage = blade::TextureUsage::COPY | blade::TextureUsage::RESOURCE;
}
AtlasTextureKind::Path => {
format = blade::TextureFormat::R16Float;
usage = blade::TextureUsage::COPY
| blade::TextureUsage::RESOURCE
| blade::TextureUsage::TARGET;
}
}
let raw = self.gpu.create_texture(blade::TextureDesc {
name: "",
format,
size: blade::Extent {
width: size.width.into(),
height: size.height.into(),
depth: 1,
},
array_layer_count: 1,
mip_level_count: 1,
dimension: blade::TextureDimension::D2,
usage,
});
let textures = match kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
AtlasTextureKind::Path => &mut self.path_textures,
};
let atlas_texture = BladeAtlasTexture {
id: AtlasTextureId {
index: textures.len() as u32,
kind,
},
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
format,
raw,
};
textures.push(atlas_texture);
textures.last_mut().unwrap()
}
fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
let textures = match id.kind {
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
crate::AtlasTextureKind::Path => &self.path_textures,
};
let texture = &textures[id.index as usize];
let src_data = self.upload_belt.alloc_data(bytes, &self.gpu);
let mut transfers = self.gpu_encoder.transfer();
transfers.copy_buffer_to_texture(
src_data,
bounds.size.width.to_bytes(texture.bytes_per_pixel()),
blade::TexturePiece {
texture: texture.raw,
mip_level: 0,
array_layer: 0,
origin: [bounds.origin.x.into(), bounds.origin.y.into(), 0],
},
blade::Extent {
width: bounds.size.width.into(),
height: bounds.size.height.into(),
depth: 1,
},
);
}
}
struct BladeAtlasTexture {
id: AtlasTextureId,
allocator: BucketedAtlasAllocator,
raw: blade::Texture,
format: blade::TextureFormat,
}
impl BladeAtlasTexture {
fn clear(&mut self) {
self.allocator.clear();
}
fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
let allocation = self.allocator.allocate(size.into())?;
let tile = AtlasTile {
texture_id: self.id,
tile_id: allocation.id.into(),
bounds: Bounds {
origin: allocation.rectangle.min.into(),
size,
},
};
Some(tile)
}
fn bytes_per_pixel(&self) -> u8 {
self.format.block_info().size
}
}
impl From<Size<DevicePixels>> for etagere::Size {
fn from(size: Size<DevicePixels>) -> Self {
etagere::Size::new(size.width.into(), size.height.into())
}
}
impl From<etagere::Point> for Point<DevicePixels> {
fn from(value: etagere::Point) -> Self {
Point {
x: DevicePixels::from(value.x),
y: DevicePixels::from(value.y),
}
}
}
impl From<etagere::Size> for Size<DevicePixels> {
fn from(size: etagere::Size) -> Self {
Size {
width: DevicePixels::from(size.width),
height: DevicePixels::from(size.height),
}
}
}
impl From<etagere::Rectangle> for Bounds<DevicePixels> {
fn from(rectangle: etagere::Rectangle) -> Self {
Bounds {
origin: rectangle.min.into(),
size: rectangle.size().into(),
}
}
}

View file

@ -0,0 +1,84 @@
struct ReusableBuffer {
raw: blade::Buffer,
size: u64,
}
pub struct BladeBeltDescriptor {
pub memory: blade::Memory,
pub min_chunk_size: u64,
}
/// A belt of buffers, used by the BladeAtlas to cheaply
/// find staging space for uploads.
pub struct BladeBelt {
desc: BladeBeltDescriptor,
buffers: Vec<(ReusableBuffer, blade::SyncPoint)>,
active: Vec<(ReusableBuffer, u64)>,
}
impl BladeBelt {
pub fn new(desc: BladeBeltDescriptor) -> Self {
Self {
desc,
buffers: Vec::new(),
active: Vec::new(),
}
}
pub fn destroy(&mut self, gpu: &blade::Context) {
for (buffer, _) in self.buffers.drain(..) {
gpu.destroy_buffer(buffer.raw);
}
for (buffer, _) in self.active.drain(..) {
gpu.destroy_buffer(buffer.raw);
}
}
pub fn alloc(&mut self, size: u64, gpu: &blade::Context) -> blade::BufferPiece {
for &mut (ref rb, ref mut offset) in self.active.iter_mut() {
if *offset + size <= rb.size {
let piece = rb.raw.at(*offset);
*offset += size;
return piece;
}
}
let index_maybe = self
.buffers
.iter()
.position(|&(ref rb, ref sp)| size <= rb.size && gpu.wait_for(sp, 0));
if let Some(index) = index_maybe {
let (rb, _) = self.buffers.remove(index);
let piece = rb.raw.into();
self.active.push((rb, size));
return piece;
}
let chunk_index = self.buffers.len() + self.active.len();
let chunk_size = size.max(self.desc.min_chunk_size);
let chunk = gpu.create_buffer(blade::BufferDesc {
name: &format!("chunk-{}", chunk_index),
size: chunk_size,
memory: self.desc.memory,
});
let rb = ReusableBuffer {
raw: chunk,
size: chunk_size,
};
self.active.push((rb, size));
chunk.into()
}
pub fn alloc_data(&mut self, data: &[u8], gpu: &blade::Context) -> blade::BufferPiece {
let bp = self.alloc(data.len() as u64, gpu);
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr(), bp.data(), data.len());
}
bp
}
pub fn flush(&mut self, sp: &blade::SyncPoint) {
self.buffers
.extend(self.active.drain(..).map(|(rb, _)| (rb, sp.clone())));
}
}

View file

@ -0,0 +1,23 @@
use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay};
use anyhow::Result;
use uuid::Uuid;
#[derive(Debug)]
pub(crate) struct LinuxDisplay;
impl PlatformDisplay for LinuxDisplay {
fn id(&self) -> DisplayId {
DisplayId(0)
}
fn uuid(&self) -> Result<Uuid> {
Ok(Uuid::from_bytes([0; 16]))
}
fn bounds(&self) -> Bounds<GlobalPixels> {
Bounds {
origin: point(GlobalPixels(0.0), GlobalPixels(0.0)),
size: size(GlobalPixels(100.0), GlobalPixels(100.0)),
}
}
}

View file

@ -2,9 +2,9 @@
use crate::{ use crate::{
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
ForegroundExecutor, Keymap, LinuxDispatcher, LinuxTextSystem, Menu, PathPromptOptions, ForegroundExecutor, Keymap, LinuxDispatcher, LinuxDisplay, LinuxTextSystem, LinuxWindow, Menu,
Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, PlatformWindow, Result, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem,
SemanticVersion, Task, WindowOptions, PlatformWindow, Result, SemanticVersion, Task, WindowOptions,
}; };
use futures::channel::oneshot; use futures::channel::oneshot;
@ -21,6 +21,7 @@ use time::UtcOffset;
pub(crate) struct LinuxPlatform(Mutex<LinuxPlatformState>); pub(crate) struct LinuxPlatform(Mutex<LinuxPlatformState>);
pub(crate) struct LinuxPlatformState { pub(crate) struct LinuxPlatformState {
gpu: Arc<blade::Context>,
background_executor: BackgroundExecutor, background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor, foreground_executor: ForegroundExecutor,
text_system: Arc<LinuxTextSystem>, text_system: Arc<LinuxTextSystem>,
@ -35,7 +36,17 @@ impl Default for LinuxPlatform {
impl LinuxPlatform { impl LinuxPlatform {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
let dispatcher = Arc::new(LinuxDispatcher::new()); let dispatcher = Arc::new(LinuxDispatcher::new());
let gpu = Arc::new(
unsafe {
blade::Context::init(blade::ContextDesc {
validation: true, //FIXME
capture: false,
})
}
.unwrap(),
);
Self(Mutex::new(LinuxPlatformState { Self(Mutex::new(LinuxPlatformState {
gpu,
background_executor: BackgroundExecutor::new(dispatcher.clone()), background_executor: BackgroundExecutor::new(dispatcher.clone()),
foreground_executor: ForegroundExecutor::new(dispatcher), foreground_executor: ForegroundExecutor::new(dispatcher),
text_system: Arc::new(LinuxTextSystem::new()), text_system: Arc::new(LinuxTextSystem::new()),
@ -57,43 +68,31 @@ impl Platform for LinuxPlatform {
} }
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) { fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
unimplemented!() on_finish_launching()
} }
fn quit(&self) { fn quit(&self) {}
unimplemented!()
}
fn restart(&self) { fn restart(&self) {}
unimplemented!()
}
fn activate(&self, ignoring_other_apps: bool) { fn activate(&self, ignoring_other_apps: bool) {}
unimplemented!()
}
fn hide(&self) { fn hide(&self) {}
unimplemented!()
}
fn hide_other_apps(&self) { fn hide_other_apps(&self) {}
unimplemented!()
}
fn unhide_other_apps(&self) { fn unhide_other_apps(&self) {}
unimplemented!()
}
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> { fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
unimplemented!() Vec::new()
} }
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> { fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
unimplemented!() None
} }
fn active_window(&self) -> Option<AnyWindowHandle> { fn active_window(&self) -> Option<AnyWindowHandle> {
unimplemented!() None
} }
fn open_window( fn open_window(
@ -101,7 +100,13 @@ impl Platform for LinuxPlatform {
handle: AnyWindowHandle, handle: AnyWindowHandle,
options: WindowOptions, options: WindowOptions,
) -> Box<dyn PlatformWindow> { ) -> Box<dyn PlatformWindow> {
unimplemented!() let lock = self.0.lock();
Box::new(LinuxWindow::new(
options,
handle,
Rc::new(LinuxDisplay),
&lock.gpu,
))
} }
fn set_display_link_output_callback( fn set_display_link_output_callback(
@ -112,21 +117,13 @@ impl Platform for LinuxPlatform {
unimplemented!() unimplemented!()
} }
fn start_display_link(&self, display_id: DisplayId) { fn start_display_link(&self, display_id: DisplayId) {}
unimplemented!()
}
fn stop_display_link(&self, display_id: DisplayId) { fn stop_display_link(&self, display_id: DisplayId) {}
unimplemented!()
}
fn open_url(&self, url: &str) { fn open_url(&self, url: &str) {}
unimplemented!()
}
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) { fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {}
unimplemented!()
}
fn prompt_for_paths( fn prompt_for_paths(
&self, &self,
@ -139,88 +136,72 @@ impl Platform for LinuxPlatform {
unimplemented!() unimplemented!()
} }
fn reveal_path(&self, path: &Path) { fn reveal_path(&self, path: &Path) {}
unimplemented!()
}
fn on_become_active(&self, callback: Box<dyn FnMut()>) { fn on_become_active(&self, callback: Box<dyn FnMut()>) {}
unimplemented!()
}
fn on_resign_active(&self, callback: Box<dyn FnMut()>) { fn on_resign_active(&self, callback: Box<dyn FnMut()>) {}
unimplemented!()
}
fn on_quit(&self, callback: Box<dyn FnMut()>) { fn on_quit(&self, callback: Box<dyn FnMut()>) {}
unimplemented!()
}
fn on_reopen(&self, callback: Box<dyn FnMut()>) { fn on_reopen(&self, callback: Box<dyn FnMut()>) {}
unimplemented!()
}
fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) { fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {}
unimplemented!()
}
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) { fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {}
unimplemented!()
}
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) { fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {}
unimplemented!()
}
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) { fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {}
unimplemented!()
}
fn os_name(&self) -> &'static str { fn os_name(&self) -> &'static str {
"Linux" "Linux"
} }
fn double_click_interval(&self) -> Duration { fn double_click_interval(&self) -> Duration {
unimplemented!() Duration::default()
} }
fn os_version(&self) -> Result<SemanticVersion> { fn os_version(&self) -> Result<SemanticVersion> {
unimplemented!() Ok(SemanticVersion {
major: 1,
minor: 0,
patch: 0,
})
} }
fn app_version(&self) -> Result<SemanticVersion> { fn app_version(&self) -> Result<SemanticVersion> {
unimplemented!() Ok(SemanticVersion {
major: 1,
minor: 0,
patch: 0,
})
} }
fn app_path(&self) -> Result<PathBuf> { fn app_path(&self) -> Result<PathBuf> {
unimplemented!() unimplemented!()
} }
fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) { fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {}
unimplemented!()
}
fn local_timezone(&self) -> UtcOffset { fn local_timezone(&self) -> UtcOffset {
unimplemented!() UtcOffset::UTC
} }
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> { fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
unimplemented!() unimplemented!()
} }
fn set_cursor_style(&self, style: CursorStyle) { fn set_cursor_style(&self, style: CursorStyle) {}
unimplemented!()
}
fn should_auto_hide_scrollbars(&self) -> bool { fn should_auto_hide_scrollbars(&self) -> bool {
unimplemented!() false
} }
fn write_to_clipboard(&self, item: ClipboardItem) { fn write_to_clipboard(&self, item: ClipboardItem) {}
unimplemented!()
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> { fn read_from_clipboard(&self) -> Option<ClipboardItem> {
unimplemented!() None
} }
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> { fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {

View file

@ -55,13 +55,13 @@ impl Default for LinuxTextSystem {
#[allow(unused)] #[allow(unused)]
impl PlatformTextSystem for LinuxTextSystem { impl PlatformTextSystem for LinuxTextSystem {
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> { fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
unimplemented!() Ok(()) //TODO
} }
fn all_font_names(&self) -> Vec<String> { fn all_font_names(&self) -> Vec<String> {
unimplemented!() Vec::new()
} }
fn all_font_families(&self) -> Vec<String> { fn all_font_families(&self) -> Vec<String> {
unimplemented!() Vec::new()
} }
fn font_id(&self, descriptor: &Font) -> Result<FontId> { fn font_id(&self, descriptor: &Font) -> Result<FontId> {
unimplemented!() unimplemented!()

View file

@ -0,0 +1,143 @@
use crate::{
px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, BladeAtlas, Bounds, KeyDownEvent,
Keystroke, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
PlatformWindow, Point, Size, TileId, WindowAppearance, WindowBounds, WindowOptions,
};
use collections::HashMap;
use parking_lot::Mutex;
use std::{
rc::{Rc, Weak},
sync::{self, Arc},
};
pub(crate) struct LinuxWindowState {
display: Rc<dyn crate::PlatformDisplay>,
sprite_atlas: Arc<BladeAtlas>,
}
#[derive(Clone)]
pub(crate) struct LinuxWindow(pub(crate) Arc<Mutex<LinuxWindowState>>);
impl LinuxWindow {
pub fn new(
options: WindowOptions,
handle: AnyWindowHandle,
display: Rc<dyn PlatformDisplay>,
gpu: &Arc<blade::Context>,
) -> Self {
Self(Arc::new(Mutex::new(LinuxWindowState {
display,
sprite_atlas: Arc::new(BladeAtlas::new(gpu)),
})))
}
}
impl PlatformWindow for LinuxWindow {
fn bounds(&self) -> WindowBounds {
unimplemented!()
}
fn content_size(&self) -> Size<Pixels> {
unimplemented!()
}
fn scale_factor(&self) -> f32 {
1.0
}
fn titlebar_height(&self) -> Pixels {
unimplemented!()
}
fn appearance(&self) -> WindowAppearance {
unimplemented!()
}
fn display(&self) -> Rc<dyn crate::PlatformDisplay> {
Rc::clone(&self.0.lock().display)
}
fn mouse_position(&self) -> Point<Pixels> {
Point::default()
}
fn modifiers(&self) -> crate::Modifiers {
crate::Modifiers::default()
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {}
fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
None
}
fn prompt(
&self,
_level: crate::PromptLevel,
_msg: &str,
_detail: Option<&str>,
_answers: &[&str],
) -> futures::channel::oneshot::Receiver<usize> {
unimplemented!()
}
fn activate(&self) {}
fn set_title(&mut self, title: &str) {}
fn set_edited(&mut self, edited: bool) {}
fn show_character_palette(&self) {
unimplemented!()
}
fn minimize(&self) {
unimplemented!()
}
fn zoom(&self) {
unimplemented!()
}
fn toggle_full_screen(&self) {
unimplemented!()
}
fn on_request_frame(&self, _callback: Box<dyn FnMut()>) {}
fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> bool>) {}
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {}
fn on_fullscreen(&self, _callback: Box<dyn FnMut(bool)>) {}
fn on_moved(&self, callback: Box<dyn FnMut()>) {}
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {}
fn on_close(&self, _callback: Box<dyn FnOnce()>) {
unimplemented!()
}
fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {
unimplemented!()
}
fn is_topmost_for_position(&self, _position: crate::Point<Pixels>) -> bool {
unimplemented!()
}
fn invalidate(&self) {}
fn draw(&self, _scene: &crate::Scene) {}
fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
self.0.lock().sprite_atlas.clone()
}
}