From 574340749b71ba54ccfdd50d0211c60bdc75693d Mon Sep 17 00:00:00 2001 From: RabbyDevs <67389402+RabbyDevs@users.noreply.github.com> Date: Thu, 17 Apr 2025 19:35:45 +0300 Subject: [PATCH] commit so that i dont have to use any code blocks :) --- iced_video_player/src/video.rs | 16 ++- src/main.rs | 217 ++++++++++++++++----------------- src/utils/img_utils.rs | 58 +++++++++ src/utils/mod.rs | 1 + 4 files changed, 177 insertions(+), 115 deletions(-) create mode 100644 src/utils/img_utils.rs create mode 100644 src/utils/mod.rs diff --git a/iced_video_player/src/video.rs b/iced_video_player/src/video.rs index 2fa9088..f9a6071 100644 --- a/iced_video_player/src/video.rs +++ b/iced_video_player/src/video.rs @@ -56,6 +56,7 @@ impl Frame { #[derive(Debug)] pub(crate) struct Internal { + // pub(crate) uri: url::Url, pub(crate) id: u64, pub(crate) bus: gst::Bus, @@ -194,6 +195,12 @@ impl Internal { #[derive(Debug)] pub struct Video(pub(crate) RwLock); +// impl Clone for Video { +// fn clone(&self) -> Self { +// Video::new(self.read().uri.clone()).unwrap() +// } +// } + impl Drop for Video { fn drop(&mut self) { let inner = self.0.get_mut().expect("failed to lock"); @@ -240,7 +247,12 @@ impl Video { let text_sink: gst::Element = pipeline.property("text-sink"); let text_sink = text_sink.downcast::().unwrap(); - Self::from_gst_pipeline(pipeline, video_sink, Some(text_sink)) + Self::from_gst_pipeline( + // uri, + pipeline, + video_sink, + Some(text_sink) + ) } /// Creates a new video based on an existing GStreamer pipeline and appsink. @@ -252,6 +264,7 @@ impl Video { /// **Note:** Many functions of [`Video`] assume a `playbin` pipeline. /// Non-`playbin` pipelines given here may not have full functionality. pub fn from_gst_pipeline( + // uri: url::Url, pipeline: gst::Pipeline, video_sink: gst_app::AppSink, text_sink: Option, @@ -414,6 +427,7 @@ impl Video { }); Ok(Video(RwLock::new(Internal { + // uri, id, bus: pipeline.bus().unwrap(), diff --git a/src/main.rs b/src/main.rs index d084a88..86c7606 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,19 @@ -// #![feature(let_chains)] use file_format::FileFormat; use ::image::ImageReader; use iced::{ - alignment::Vertical::Top, border, gradient, mouse, widget::{button, center, column, container, image, mouse_area, row, stack, text, Column, Space}, window::{self, icon, settings::PlatformSpecific, Settings}, Alignment::Center, Color, Element, Length, Renderer, Size, Task, Theme + alignment::Vertical::Top, border, gradient, mouse, widget::{button, center, column, container, image, mouse_area, row, stack, text, Column, Space}, window::{self, icon, Settings}, Alignment::Center, Color, Element, Font, Length, Point, Renderer, Size, Subscription, Task, Theme }; use iced_video_player::{Video, VideoPlayer}; use serde::{Deserialize, Serialize}; +use utils::img_utils::round_image; use std::{ - fs::{self, create_dir_all, read_to_string}, io::{Cursor, Write}, path::PathBuf, thread::sleep + env, fs::{self, create_dir_all, read_to_string}, io::{Cursor, Read, Write}, path::PathBuf, sync::Arc }; +mod utils; use tempfile::NamedTempFile; +use iced::event::{self, Event}; +use iced::keyboard::Event as KeyboardEvent; +use iced::mouse::Event as MouseEvent; #[derive(rust_embed::Embed)] #[folder = "resources"] @@ -22,7 +26,7 @@ pub fn main() -> iced::Result { .unwrap() .decode() .unwrap(); - let rgba_vec = icon_image.as_rgba8().unwrap().clone().into_vec(); + let rgba_vec = icon_image.as_rgba8().unwrap().to_vec(); let settings = Settings { decorations: false, @@ -68,7 +72,7 @@ struct State { selected_game: PossibleGames, installed_games: Vec, installed_game_servers: Vec, - db_software_installed: bool + db_software_installed: bool, } #[derive(Debug, Default, Serialize, Deserialize)] @@ -93,62 +97,54 @@ enum SaveError { #[derive(Debug, Clone)] enum Message { Loaded(Result), - HoverEnter(), - GameSelected(PossibleGames), + DragStarted, + GameSelected(PossibleGames) } impl State { - fn path() -> PathBuf { - let mut path = if let Some(project_dirs) = - directories::ProjectDirs::from("rs", "reversed-rooms", "launcher") - { - project_dirs.data_dir().into() - } else { - std::env::current_dir().unwrap_or_default() - }; + // fn path() -> PathBuf { + // path.push("launcher-state.json"); - path.push("launcher-state.json"); + // path + // } - path - } + // fn load() -> Result { + // let contents = read_to_string(Self::path()).map_err(|_| LoadError::File)?; - fn load() -> Result { - let contents = read_to_string(Self::path()).map_err(|_| LoadError::File)?; + // let saved_state: SavedState = + // serde_json::from_str(&contents).map_err(|_| LoadError::Format)?; - let saved_state: SavedState = - serde_json::from_str(&contents).map_err(|_| LoadError::Format)?; + // Ok(State { + // selected_game: PossibleGames::WutheringWaves, + // installed_games: saved_state.installed_games, + // installed_game_servers: saved_state.installed_game_servers, + // db_software_installed: saved_state.db_software_installed, + // }) + // } - Ok(State { - selected_game: PossibleGames::WutheringWaves, - installed_games: saved_state.installed_games, - installed_game_servers: saved_state.installed_game_servers, - db_software_installed: saved_state.db_software_installed, - }) - } + // async fn save(self) -> Result<(), SaveError> { + // let saved_state = SavedState { + // installed_games: self.installed_games, + // installed_game_servers: self.installed_game_servers, + // db_software_installed: self.db_software_installed, + // }; - async fn save(self) -> Result<(), SaveError> { - let saved_state = SavedState { - installed_games: self.installed_games, - installed_game_servers: self.installed_game_servers, - db_software_installed: self.db_software_installed, - }; + // let json = serde_json::to_string_pretty(&saved_state).map_err(|_| SaveError::Format)?; - let json = serde_json::to_string_pretty(&saved_state).map_err(|_| SaveError::Format)?; + // let path = Self::path(); - let path = Self::path(); + // if let Some(dir) = path.parent() { + // create_dir_all(dir).map_err(|_| SaveError::Write)?; + // } - if let Some(dir) = path.parent() { - create_dir_all(dir).map_err(|_| SaveError::Write)?; - } + // { + // fs::write(path, json.as_bytes()).map_err(|_| SaveError::Write)?; + // } - { - fs::write(path, json.as_bytes()).map_err(|_| SaveError::Write)?; - } + // sleep(std::time::Duration::from_secs(2)); - sleep(std::time::Duration::from_secs(2)); - - Ok(()) - } + // Ok(()) + // } } fn rad(deg: f32) -> f32 { @@ -164,10 +160,11 @@ fn get_game_background(game: &PossibleGames) -> Element { }; if let Some(file) = Assets::get(file_path) { - let file_format = FileFormat::from_bytes(file.data.clone()); + let data = Arc::new(file.data); + let file_format = FileFormat::from_bytes(&*data); if file_format.extension() == "mp4" { let mut temp_file = NamedTempFile::new().unwrap(); - temp_file.write_all(&file.data.clone()).unwrap(); + temp_file.write_all(&data).unwrap(); let temp_path = temp_file.path().to_str().unwrap().to_string(); match Video::new(url::Url::from_file_path(temp_path).unwrap()) { @@ -181,7 +178,7 @@ fn get_game_background(game: &PossibleGames) -> Element { }, } } else { - let img = ImageReader::new(Cursor::new(&file.data)) + let img = ImageReader::new(Cursor::new(&*data)) .with_guessed_format() .unwrap() .decode() @@ -207,10 +204,8 @@ fn get_game_icon(game: &PossibleGames) -> Element { PossibleGames::GenshinImpact => "genshinimpact-icon.png", }; if let Some(img_file) = Assets::get(file_path) { - let img = ImageReader::new(Cursor::new(img_file.data)) - .with_guessed_format() - .unwrap() - .decode() + let data_cursor = Cursor::new(img_file.data); + let img = round_image(data_cursor) .unwrap() .resize(126, 126, ::image::imageops::FilterType::Lanczos3); let handle = image::Handle::from_rgba( @@ -221,8 +216,6 @@ fn get_game_icon(game: &PossibleGames) -> Element { container(image(handle).content_fit(iced::ContentFit::Contain).height(Length::Fixed(64.0)).filter_method(image::FilterMethod::Linear)) .style(move |_| { container::Style { - // text_color: Color::from_rgba8(0, 0, 0, 1.0).into(), - // background: Some(Color::from_rgba8(255, 255, 255, 1.0).into()), border: border::rounded(20), ..container::Style::default() } @@ -233,16 +226,16 @@ fn get_game_icon(game: &PossibleGames) -> Element { } } -fn style_container(direction: f32) -> container::Style { +fn style_container(direction: f32, use_gradient: bool) -> container::Style { let angle = rad(direction); + let gradient: Option = if use_gradient { + Some(gradient::Linear::new(angle) + .add_stop(0.0, Color::from_rgba8(0, 0, 0, 0.0)) + .add_stop(1.0, Color::from_rgba8(0, 0, 0, 0.8)).into()) + } else {None}; container::Style { text_color: Color::from_rgba8(255, 255, 255, 1.0).into(), - background: Some( - gradient::Linear::new(angle) - .add_stop(0.0, Color::from_rgba8(0, 0, 0, 0.0)) - .add_stop(1.0, Color::from_rgba8(0, 0, 0, 0.8)) - .into(), - ), + background: gradient, ..container::Style::default() } } @@ -262,12 +255,18 @@ impl Launcher { *self = Launcher::Loaded(state); Task::none() }, - Message::HoverEnter() => { - Task::none() - } _ => Task::none(), }, - _ => Task::none(), + Launcher::Loaded(_) => { + match message { + Message::DragStarted => { + window::get_latest().and_then(move |id: window::Id| { + window::drag(id) + }) + }, + _ => Task::none() + } + } } } @@ -278,65 +277,55 @@ impl Launcher { get_game_icon(&PossibleGames::ZenlessZoneZero), get_game_icon(&PossibleGames::HonkaiStarRail), get_game_icon(&PossibleGames::GenshinImpact), - // text("test").size(25), - // text("test").size(25), ] .spacing(10), ) - // .padding(10) + .padding(10) .align_y(Top) .align_x(Center) - .width(Length::Fill); - - // let decorations = container(row![ - // button(content) - // ]); - - let topbar = container(row![ - text("Reversed Rooms").size(25), - Space::new(Length::Fill, Length::Fixed(0.0)), - game_selector, - Space::new(Length::Fill, Length::Fixed(0.0)), - // text("rabbydevs").size(25), - ]) - .height(Length::Fill) .width(Length::Fill) - .style(move |_| style_container(0.0)) - .padding(10); - - let bottom_bar = container(row![ - text("insert game announcements").size(25), - Space::new(Length::Fill, Length::Fixed(0.0)), - container(mouse_area(button(text("Launch").size(25)) - .padding(10) - .style(move |_, _| { - button::Style { - text_color: Color::from_rgba8(0, 0, 0, 1.0), - background: Some(Color::from_rgba8(255, 255, 255, 1.0).into()), - border: border::rounded(5), - ..button::Style::default() - } - })).interaction(iced::mouse::Interaction::Pointer)) - ]) - .width(Length::Fill) - .style(move |_theme| style_container(180.0)) - .padding(20); - - let user_area: Column = - column![topbar, Space::new(Length::Fill, Length::Fill), bottom_bar].width(Length::Fill); - - let content = container(user_area).center(Length::Fill); + .style(move |_| style_container(0.0, true)); println!("whuh"); match self { - Launcher::Loading => loading_message(), + Launcher::Loading => center(text("Loading...").size(50)).into(), Launcher::Loaded(state) => { - stack![get_game_background(&state.selected_game), content].into() + let topbar = container( + mouse_area(row![ + text("Reversed Rooms").size(25), + Space::new(Length::Fill, Length::Fixed(0.0)), + ]) + .on_press(Message::DragStarted)) + .width(Length::Fill) + .style(move |_| style_container(0.0, false)) + .padding(10); + + let bottom_bar = container(row![ + text("insert game announcements").size(25), + Space::new(Length::Fill, Length::Fixed(0.0)), + container(mouse_area(button(text("Launch").size(25)) + .padding(10) + .style(move |_, _| { + button::Style { + text_color: Color::from_rgba8(0, 0, 0, 1.0), + background: Some(Color::from_rgba8(255, 255, 255, 1.0).into()), + border: border::rounded(5), + ..button::Style::default() + } + })).interaction(iced::mouse::Interaction::Pointer)) + ]) + .width(Length::Fill) + .style(move |_theme| style_container(180.0, true)) + .padding(20); + + let user_area: Column = + column![topbar, Space::new(Length::Fill, Length::Fill), bottom_bar].width(Length::Fill); + + let content = container(user_area).center(Length::Fill); + + stack![get_game_background(&state.selected_game), game_selector, content].into() } } } } -fn loading_message<'a>() -> Element<'a, Message> { - center(text("Loading...").size(50)).into() -} diff --git a/src/utils/img_utils.rs b/src/utils/img_utils.rs new file mode 100644 index 0000000..6c64cc3 --- /dev/null +++ b/src/utils/img_utils.rs @@ -0,0 +1,58 @@ +use std::io::Cursor; + +use image::{DynamicImage, GenericImageView, ImageBuffer, ImageReader, Rgba}; + +pub fn is_in_rounded_rect(x: u32, y: u32, width: u32, height: u32, radius: f32) -> bool { + let x = x as f32; + let y = y as f32; + let width = width as f32; + let height = height as f32; + + if x < radius && y < radius { + let dx = x - radius; + let dy = y - radius; + let distance = (dx.powi(2) + dy.powi(2)).sqrt(); + return distance <= radius; + } else if x > width - radius && y < radius { + let dx = x - (width - radius); + let dy = y - radius; + let distance = (dx.powi(2) + dy.powi(2)).sqrt(); + return distance <= radius; + } else if x < radius && y > height - radius { + let dx = x - radius; + let dy = y - (height - radius); + let distance = (dx.powi(2) + dy.powi(2)).sqrt(); + return distance <= radius; + } else if x > width - radius && y > height - radius { + let dx = x - (width - radius); + let dy = y - (height - radius); + let distance = (dx.powi(2) + dy.powi(2)).sqrt(); + return distance <= radius; + } + + x >= 0.0 && x < width && y >= 0.0 && y < height +} + +pub fn round_image(img_data: Cursor>) -> Result> { + let img = ImageReader::new(img_data) + .with_guessed_format() + .unwrap() + .decode() + .unwrap(); + + let (width, height) = img.dimensions(); + + let mut rounded_img = ImageBuffer::new(width, height); + + let radius = 50.0; + + for (x, y, pixel) in img.pixels() { + if is_in_rounded_rect(x, y, width, height, radius) { + rounded_img.put_pixel(x, y, Rgba(pixel.0)); + } else { + rounded_img.put_pixel(x, y, Rgba([0, 0, 0, 0])); + } + } + + Ok(DynamicImage::ImageRgba8(rounded_img)) +} \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..ae16edc --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod img_utils; \ No newline at end of file