use iced::alignment; use iced::executor; use iced::keyboard; use iced::theme; use iced::widget::{button, column, container, image, row, text, text_input}; use iced::window; use iced::window::screenshot::{self, Screenshot}; use iced::{ Alignment, Application, Command, ContentFit, Element, Length, Rectangle, Renderer, Subscription, Theme, }; use ::image as img; use ::image::ColorType; fn main() -> iced::Result { tracing_subscriber::fmt::init(); Example::run(iced::Settings::default()) } struct Example { screenshot: Option, saved_png_path: Option>, png_saving: bool, crop_error: Option, x_input_value: Option, y_input_value: Option, width_input_value: Option, height_input_value: Option, } #[derive(Clone, Debug)] enum Message { Crop, Screenshot, ScreenshotData(Screenshot), Png, PngSaved(Result), XInputChanged(Option), YInputChanged(Option), WidthInputChanged(Option), HeightInputChanged(Option), } impl Application for Example { type Executor = executor::Default; type Message = Message; type Theme = Theme; type Flags = (); fn new(_flags: Self::Flags) -> (Self, Command) { ( Example { screenshot: None, saved_png_path: None, png_saving: false, crop_error: None, x_input_value: None, y_input_value: None, width_input_value: None, height_input_value: None, }, Command::none(), ) } fn title(&self) -> String { "Screenshot".to_string() } fn update(&mut self, message: Self::Message) -> Command { match message { Message::Screenshot => { return iced::window::screenshot( window::Id::MAIN, Message::ScreenshotData, ); } Message::ScreenshotData(screenshot) => { self.screenshot = Some(screenshot); } Message::Png => { if let Some(screenshot) = &self.screenshot { self.png_saving = true; return Command::perform( save_to_png(screenshot.clone()), Message::PngSaved, ); } } Message::PngSaved(res) => { self.png_saving = false; self.saved_png_path = Some(res); } Message::XInputChanged(new_value) => { self.x_input_value = new_value; } Message::YInputChanged(new_value) => { self.y_input_value = new_value; } Message::WidthInputChanged(new_value) => { self.width_input_value = new_value; } Message::HeightInputChanged(new_value) => { self.height_input_value = new_value; } Message::Crop => { if let Some(screenshot) = &self.screenshot { let cropped = screenshot.crop(Rectangle:: { x: self.x_input_value.unwrap_or(0), y: self.y_input_value.unwrap_or(0), width: self.width_input_value.unwrap_or(0), height: self.height_input_value.unwrap_or(0), }); match cropped { Ok(screenshot) => { self.screenshot = Some(screenshot); self.crop_error = None; } Err(crop_error) => { self.crop_error = Some(crop_error); } } } } } Command::none() } fn view(&self) -> Element<'_, Self::Message, Renderer> { let image: Element = if let Some(screenshot) = &self.screenshot { image(image::Handle::from_pixels( screenshot.size.width, screenshot.size.height, screenshot.clone(), )) .content_fit(ContentFit::Contain) .width(Length::Fill) .height(Length::Fill) .into() } else { text("Press the button to take a screenshot!").into() }; let image = container(image) .padding(10) .style(theme::Container::Box) .width(Length::FillPortion(2)) .height(Length::Fill) .center_x() .center_y(); let crop_origin_controls = row![ text("X:") .vertical_alignment(alignment::Vertical::Center) .width(30), numeric_input("0", self.x_input_value).map(Message::XInputChanged), text("Y:") .vertical_alignment(alignment::Vertical::Center) .width(30), numeric_input("0", self.y_input_value).map(Message::YInputChanged) ] .spacing(10) .align_items(Alignment::Center); let crop_dimension_controls = row![ text("W:") .vertical_alignment(alignment::Vertical::Center) .width(30), numeric_input("0", self.width_input_value) .map(Message::WidthInputChanged), text("H:") .vertical_alignment(alignment::Vertical::Center) .width(30), numeric_input("0", self.height_input_value) .map(Message::HeightInputChanged) ] .spacing(10) .align_items(Alignment::Center); let mut crop_controls = column![crop_origin_controls, crop_dimension_controls] .spacing(10) .align_items(Alignment::Center); if let Some(crop_error) = &self.crop_error { crop_controls = crop_controls.push(text(format!("Crop error! \n{crop_error}"))); } let mut controls = column![ column![ button(centered_text("Screenshot!")) .padding([10, 20, 10, 20]) .width(Length::Fill) .on_press(Message::Screenshot), if !self.png_saving { button(centered_text("Save as png")).on_press_maybe( self.screenshot.is_some().then(|| Message::Png), ) } else { button(centered_text("Saving...")) .style(theme::Button::Secondary) } .style(theme::Button::Secondary) .padding([10, 20, 10, 20]) .width(Length::Fill) ] .spacing(10), column![ crop_controls, button(centered_text("Crop")) .on_press(Message::Crop) .style(theme::Button::Destructive) .padding([10, 20, 10, 20]) .width(Length::Fill), ] .spacing(10) .align_items(Alignment::Center), ] .spacing(40); if let Some(png_result) = &self.saved_png_path { let msg = match png_result { Ok(path) => format!("Png saved as: {path:?}!"), Err(msg) => { format!("Png could not be saved due to:\n{msg:?}") } }; controls = controls.push(text(msg)); } let side_content = container(controls) .align_x(alignment::Horizontal::Center) .width(Length::FillPortion(1)) .height(Length::Fill) .center_y() .center_x(); let content = row![side_content, image] .spacing(10) .width(Length::Fill) .height(Length::Fill) .align_items(Alignment::Center); container(content) .width(Length::Fill) .height(Length::Fill) .padding(10) .center_x() .center_y() .into() } fn subscription(&self) -> Subscription { use keyboard::key; keyboard::on_key_press(|key, _modifiers| { if let keyboard::Key::Named(key::Named::F5) = key { Some(Message::Screenshot) } else { None } }) } } async fn save_to_png(screenshot: Screenshot) -> Result { let path = "screenshot.png".to_string(); tokio::task::spawn_blocking(move || { img::save_buffer( &path, &screenshot.bytes, screenshot.size.width, screenshot.size.height, ColorType::Rgba8, ) .map(|_| path) .map_err(|err| PngError(format!("{err:?}"))) }) .await .expect("Blocking task to finish") } #[derive(Clone, Debug)] struct PngError(String); fn numeric_input( placeholder: &str, value: Option, ) -> Element<'_, Option> { text_input( placeholder, &value.as_ref().map(ToString::to_string).unwrap_or_default(), ) .on_input(move |text| { if text.is_empty() { None } else if let Ok(new_value) = text.parse() { Some(new_value) } else { value } }) .width(40) .into() } fn centered_text(content: &str) -> Element<'_, Message> { text(content) .width(Length::Fill) .horizontal_alignment(alignment::Horizontal::Center) .into() }