Detailed changes
@@ -26,7 +26,7 @@ anyhow.workspace = true
async-task = "4.7"
backtrace = { version = "0.3", optional = true }
bitflags = "2.4.0"
-blade-graphics = "0.3"
+blade = { package = "blade-graphics", version = "0.3" }
blade-macros = "0.2"
collections = { path = "../collections" }
ctor.workspace = true
@@ -1,7 +1,16 @@
+mod blade_atlas;
+mod blade_belt;
mod dispatcher;
+mod display;
mod platform;
mod text_system;
+mod window;
+pub(crate) use blade_atlas::*;
pub(crate) use dispatcher::*;
+pub(crate) use display::*;
pub(crate) use platform::*;
pub(crate) use text_system::*;
+pub(crate) use window::*;
+
+use blade_belt::*;
@@ -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(),
+ }
+ }
+}
@@ -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())));
+ }
+}
@@ -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)),
+ }
+ }
+}
@@ -2,9 +2,9 @@
use crate::{
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
- ForegroundExecutor, Keymap, LinuxDispatcher, LinuxTextSystem, Menu, PathPromptOptions,
- Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, PlatformWindow, Result,
- SemanticVersion, Task, WindowOptions,
+ ForegroundExecutor, Keymap, LinuxDispatcher, LinuxDisplay, LinuxTextSystem, LinuxWindow, Menu,
+ PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem,
+ PlatformWindow, Result, SemanticVersion, Task, WindowOptions,
};
use futures::channel::oneshot;
@@ -21,6 +21,7 @@ use time::UtcOffset;
pub(crate) struct LinuxPlatform(Mutex<LinuxPlatformState>);
pub(crate) struct LinuxPlatformState {
+ gpu: Arc<blade::Context>,
background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor,
text_system: Arc<LinuxTextSystem>,
@@ -35,7 +36,17 @@ impl Default for LinuxPlatform {
impl LinuxPlatform {
pub(crate) fn new() -> Self {
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 {
+ gpu,
background_executor: BackgroundExecutor::new(dispatcher.clone()),
foreground_executor: ForegroundExecutor::new(dispatcher),
text_system: Arc::new(LinuxTextSystem::new()),
@@ -57,43 +68,31 @@ impl Platform for LinuxPlatform {
}
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
- unimplemented!()
+ on_finish_launching()
}
- fn quit(&self) {
- unimplemented!()
- }
+ fn quit(&self) {}
- fn restart(&self) {
- unimplemented!()
- }
+ fn restart(&self) {}
- fn activate(&self, ignoring_other_apps: bool) {
- unimplemented!()
- }
+ fn activate(&self, ignoring_other_apps: bool) {}
- fn hide(&self) {
- unimplemented!()
- }
+ fn hide(&self) {}
- fn hide_other_apps(&self) {
- unimplemented!()
- }
+ fn hide_other_apps(&self) {}
- fn unhide_other_apps(&self) {
- unimplemented!()
- }
+ fn unhide_other_apps(&self) {}
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
- unimplemented!()
+ Vec::new()
}
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
- unimplemented!()
+ None
}
fn active_window(&self) -> Option<AnyWindowHandle> {
- unimplemented!()
+ None
}
fn open_window(
@@ -101,7 +100,13 @@ impl Platform for LinuxPlatform {
handle: AnyWindowHandle,
options: WindowOptions,
) -> 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(
@@ -112,21 +117,13 @@ impl Platform for LinuxPlatform {
unimplemented!()
}
- fn start_display_link(&self, display_id: DisplayId) {
- unimplemented!()
- }
+ fn start_display_link(&self, display_id: DisplayId) {}
- fn stop_display_link(&self, display_id: DisplayId) {
- unimplemented!()
- }
+ fn stop_display_link(&self, display_id: DisplayId) {}
- fn open_url(&self, url: &str) {
- unimplemented!()
- }
+ fn open_url(&self, url: &str) {}
- fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
- unimplemented!()
- }
+ fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {}
fn prompt_for_paths(
&self,
@@ -139,88 +136,72 @@ impl Platform for LinuxPlatform {
unimplemented!()
}
- fn reveal_path(&self, path: &Path) {
- unimplemented!()
- }
+ fn reveal_path(&self, path: &Path) {}
- fn on_become_active(&self, callback: Box<dyn FnMut()>) {
- unimplemented!()
- }
+ fn on_become_active(&self, callback: Box<dyn FnMut()>) {}
- fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
- unimplemented!()
- }
+ fn on_resign_active(&self, callback: Box<dyn FnMut()>) {}
- fn on_quit(&self, callback: Box<dyn FnMut()>) {
- unimplemented!()
- }
+ fn on_quit(&self, callback: Box<dyn FnMut()>) {}
- fn on_reopen(&self, callback: Box<dyn FnMut()>) {
- unimplemented!()
- }
+ fn on_reopen(&self, callback: Box<dyn FnMut()>) {}
- fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
- unimplemented!()
- }
+ fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {}
- fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
- unimplemented!()
- }
+ fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {}
- fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
- unimplemented!()
- }
+ fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {}
- fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
- unimplemented!()
- }
+ fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {}
fn os_name(&self) -> &'static str {
"Linux"
}
fn double_click_interval(&self) -> Duration {
- unimplemented!()
+ Duration::default()
}
fn os_version(&self) -> Result<SemanticVersion> {
- unimplemented!()
+ Ok(SemanticVersion {
+ major: 1,
+ minor: 0,
+ patch: 0,
+ })
}
fn app_version(&self) -> Result<SemanticVersion> {
- unimplemented!()
+ Ok(SemanticVersion {
+ major: 1,
+ minor: 0,
+ patch: 0,
+ })
}
fn app_path(&self) -> Result<PathBuf> {
unimplemented!()
}
- fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {
- unimplemented!()
- }
+ fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {}
fn local_timezone(&self) -> UtcOffset {
- unimplemented!()
+ UtcOffset::UTC
}
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
unimplemented!()
}
- fn set_cursor_style(&self, style: CursorStyle) {
- unimplemented!()
- }
+ fn set_cursor_style(&self, style: CursorStyle) {}
fn should_auto_hide_scrollbars(&self) -> bool {
- unimplemented!()
+ false
}
- fn write_to_clipboard(&self, item: ClipboardItem) {
- unimplemented!()
- }
+ fn write_to_clipboard(&self, item: ClipboardItem) {}
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
- unimplemented!()
+ None
}
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
@@ -55,13 +55,13 @@ impl Default for LinuxTextSystem {
#[allow(unused)]
impl PlatformTextSystem for LinuxTextSystem {
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
- unimplemented!()
+ Ok(()) //TODO
}
fn all_font_names(&self) -> Vec<String> {
- unimplemented!()
+ Vec::new()
}
fn all_font_families(&self) -> Vec<String> {
- unimplemented!()
+ Vec::new()
}
fn font_id(&self, descriptor: &Font) -> Result<FontId> {
unimplemented!()
@@ -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()
+ }
+}