diff --git a/examples/sctk_subsurface_img/Cargo.toml b/examples/sctk_subsurface_img/Cargo.toml new file mode 100644 index 00000000..6ac531c1 --- /dev/null +++ b/examples/sctk_subsurface_img/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "sctk_subsurface_img" +version = "0.1.0" +edition = "2021" + +[dependencies] +iced = { path = "../..", default-features = false, features = [ + "tokio", + "wayland", + "winit", + "debug", + "tiny-skia", + "image", +] } +iced_runtime = { path = "../../runtime" } +env_logger = "0.10" +futures-channel = "0.3.29" +calloop = "0.13" +rustix = { version = "0.38.30", features = ["fs", "shm"] } +cctk.workspace = true +image = { workspace = true, features = ["png"] } diff --git a/examples/sctk_subsurface_img/src/main.rs b/examples/sctk_subsurface_img/src/main.rs new file mode 100644 index 00000000..feef39eb --- /dev/null +++ b/examples/sctk_subsurface_img/src/main.rs @@ -0,0 +1,135 @@ +use cctk::sctk::reexports::client::protocol::wl_shm; +use iced::{ + platform_specific::shell::subsurface_widget::{ + self, Shmbuf, SubsurfaceBuffer, + }, + window::{self, Id, Settings}, + Element, Length, Subscription, Task, +}; +use image::{ImageReader, Pixel}; +use rustix::{io::Errno, shm::ShmOFlags}; +use std::{ + env, + os::fd::OwnedFd, + path::Path, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; + +fn main() -> iced::Result { + let args = env::args(); + if args.len() != 2 { + eprintln!("usage: sctk_subsurface_img [image path]"); + return Ok(()); + } + let path = args.skip(1).next().unwrap(); + if !Path::new(&path).exists() { + eprintln!("File `{path}` not found."); + return Ok(()); + } + + iced::daemon( + SubsurfaceApp::title, + SubsurfaceApp::update, + SubsurfaceApp::view, + ) + .subscription(SubsurfaceApp::subscription) + .run_with(|| SubsurfaceApp::new(path)) +} + +#[derive(Debug, Clone)] +struct SubsurfaceApp { + path: String, + buffer: SubsurfaceBuffer, +} + +#[derive(Debug, Clone)] +pub enum Message { + Id(Id), +} + +impl SubsurfaceApp { + fn new(path: String) -> (SubsurfaceApp, Task) { + let img = ImageReader::open(&path) + .unwrap() + .decode() + .unwrap() + .to_rgba8(); + let fd = create_memfile().unwrap(); + for pixel in img.pixels() { + let [r, g, b, a] = <[u8; 4]>::try_from(pixel.channels()).unwrap(); + rustix::io::write(&fd, &[b, g, r, a]).unwrap(); + } + let shmbuf = Shmbuf { + fd, + offset: 0, + width: img.width() as i32, + height: img.height() as i32, + stride: img.width() as i32 * 4, + format: wl_shm::Format::Xrgb8888, + }; + let buffer = SubsurfaceBuffer::new(Arc::new(shmbuf.into())).0; + + ( + SubsurfaceApp { path, buffer }, + iced::window::open(Settings { + ..Default::default() + }) + .1 + .map(Message::Id), + ) + } + + fn title(&self, _id: window::Id) -> String { + String::from("SubsurfaceApp") + } + + fn update(&mut self, message: Message) -> Task { + match message { + Message::Id(_) => {} + } + Task::none() + } + + fn view(&self, _id: window::Id) -> Element { + // TODO compare side-by-side with image widget; key bind to toggle? + let image = subsurface_widget::Subsurface::new(self.buffer.clone()) + .content_fit(iced::ContentFit::None); + /* + let image = iced::widget::image::Image::new(&self.path) + .content_fit(iced::ContentFit::None); + */ + iced::widget::scrollable(image).into() + } + + fn subscription(&self) -> Subscription { + Subscription::none() + } +} + +fn create_memfile() -> rustix::io::Result { + loop { + let flags = ShmOFlags::CREATE | ShmOFlags::EXCL | ShmOFlags::RDWR; + + let time = SystemTime::now(); + let name = format!( + "/iced-sctk-{}", + time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() + ); + + match rustix::io::retry_on_intr(|| { + rustix::shm::shm_open(&name, flags, 0600.into()) + }) { + Ok(fd) => match rustix::shm::shm_unlink(&name) { + Ok(_) => return Ok(fd), + Err(errno) => { + return Err(errno.into()); + } + }, + Err(Errno::EXIST) => { + continue; + } + Err(err) => return Err(err.into()), + } + } +}