commit so that i dont have to use any code blocks :)

This commit is contained in:
RabbyDevs 2025-04-17 19:35:45 +03:00
parent bd72d0a037
commit 574340749b
4 changed files with 177 additions and 115 deletions

View file

@ -56,6 +56,7 @@ impl Frame {
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Internal { pub(crate) struct Internal {
// pub(crate) uri: url::Url,
pub(crate) id: u64, pub(crate) id: u64,
pub(crate) bus: gst::Bus, pub(crate) bus: gst::Bus,
@ -194,6 +195,12 @@ impl Internal {
#[derive(Debug)] #[derive(Debug)]
pub struct Video(pub(crate) RwLock<Internal>); pub struct Video(pub(crate) RwLock<Internal>);
// impl Clone for Video {
// fn clone(&self) -> Self {
// Video::new(self.read().uri.clone()).unwrap()
// }
// }
impl Drop for Video { impl Drop for Video {
fn drop(&mut self) { fn drop(&mut self) {
let inner = self.0.get_mut().expect("failed to lock"); 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: gst::Element = pipeline.property("text-sink");
let text_sink = text_sink.downcast::<gst_app::AppSink>().unwrap(); let text_sink = text_sink.downcast::<gst_app::AppSink>().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. /// 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. /// **Note:** Many functions of [`Video`] assume a `playbin` pipeline.
/// Non-`playbin` pipelines given here may not have full functionality. /// Non-`playbin` pipelines given here may not have full functionality.
pub fn from_gst_pipeline( pub fn from_gst_pipeline(
// uri: url::Url,
pipeline: gst::Pipeline, pipeline: gst::Pipeline,
video_sink: gst_app::AppSink, video_sink: gst_app::AppSink,
text_sink: Option<gst_app::AppSink>, text_sink: Option<gst_app::AppSink>,
@ -414,6 +427,7 @@ impl Video {
}); });
Ok(Video(RwLock::new(Internal { Ok(Video(RwLock::new(Internal {
// uri,
id, id,
bus: pipeline.bus().unwrap(), bus: pipeline.bus().unwrap(),

View file

@ -1,15 +1,19 @@
// #![feature(let_chains)]
use file_format::FileFormat; use file_format::FileFormat;
use ::image::ImageReader; use ::image::ImageReader;
use iced::{ 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 iced_video_player::{Video, VideoPlayer};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use utils::img_utils::round_image;
use std::{ 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 tempfile::NamedTempFile;
use iced::event::{self, Event};
use iced::keyboard::Event as KeyboardEvent;
use iced::mouse::Event as MouseEvent;
#[derive(rust_embed::Embed)] #[derive(rust_embed::Embed)]
#[folder = "resources"] #[folder = "resources"]
@ -22,7 +26,7 @@ pub fn main() -> iced::Result {
.unwrap() .unwrap()
.decode() .decode()
.unwrap(); .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 { let settings = Settings {
decorations: false, decorations: false,
@ -68,7 +72,7 @@ struct State {
selected_game: PossibleGames, selected_game: PossibleGames,
installed_games: Vec<PossibleGames>, installed_games: Vec<PossibleGames>,
installed_game_servers: Vec<PossibleGames>, installed_game_servers: Vec<PossibleGames>,
db_software_installed: bool db_software_installed: bool,
} }
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Default, Serialize, Deserialize)]
@ -93,62 +97,54 @@ enum SaveError {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Message { enum Message {
Loaded(Result<State, LoadError>), Loaded(Result<State, LoadError>),
HoverEnter(), DragStarted,
GameSelected(PossibleGames), GameSelected(PossibleGames)
} }
impl State { impl State {
fn path() -> PathBuf { // fn path() -> PathBuf {
let mut path = if let Some(project_dirs) = // path.push("launcher-state.json");
directories::ProjectDirs::from("rs", "reversed-rooms", "launcher")
{
project_dirs.data_dir().into()
} else {
std::env::current_dir().unwrap_or_default()
};
path.push("launcher-state.json"); // path
// }
path // fn load() -> Result<State, LoadError> {
} // let contents = read_to_string(Self::path()).map_err(|_| LoadError::File)?;
fn load() -> Result<State, LoadError> { // let saved_state: SavedState =
let contents = read_to_string(Self::path()).map_err(|_| LoadError::File)?; // serde_json::from_str(&contents).map_err(|_| LoadError::Format)?;
let saved_state: SavedState = // Ok(State {
serde_json::from_str(&contents).map_err(|_| LoadError::Format)?; // 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 { // async fn save(self) -> Result<(), SaveError> {
selected_game: PossibleGames::WutheringWaves, // let saved_state = SavedState {
installed_games: saved_state.installed_games, // installed_games: self.installed_games,
installed_game_servers: saved_state.installed_game_servers, // installed_game_servers: self.installed_game_servers,
db_software_installed: saved_state.db_software_installed, // db_software_installed: self.db_software_installed,
}) // };
}
async fn save(self) -> Result<(), SaveError> { // let json = serde_json::to_string_pretty(&saved_state).map_err(|_| SaveError::Format)?;
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 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)?;
} // }
{ // sleep(std::time::Duration::from_secs(2));
fs::write(path, json.as_bytes()).map_err(|_| SaveError::Write)?;
}
sleep(std::time::Duration::from_secs(2)); // Ok(())
// }
Ok(())
}
} }
fn rad(deg: f32) -> f32 { fn rad(deg: f32) -> f32 {
@ -164,10 +160,11 @@ fn get_game_background(game: &PossibleGames) -> Element<Message> {
}; };
if let Some(file) = Assets::get(file_path) { 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" { if file_format.extension() == "mp4" {
let mut temp_file = NamedTempFile::new().unwrap(); 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(); let temp_path = temp_file.path().to_str().unwrap().to_string();
match Video::new(url::Url::from_file_path(temp_path).unwrap()) { match Video::new(url::Url::from_file_path(temp_path).unwrap()) {
@ -181,7 +178,7 @@ fn get_game_background(game: &PossibleGames) -> Element<Message> {
}, },
} }
} else { } else {
let img = ImageReader::new(Cursor::new(&file.data)) let img = ImageReader::new(Cursor::new(&*data))
.with_guessed_format() .with_guessed_format()
.unwrap() .unwrap()
.decode() .decode()
@ -207,10 +204,8 @@ fn get_game_icon(game: &PossibleGames) -> Element<Message> {
PossibleGames::GenshinImpact => "genshinimpact-icon.png", PossibleGames::GenshinImpact => "genshinimpact-icon.png",
}; };
if let Some(img_file) = Assets::get(file_path) { if let Some(img_file) = Assets::get(file_path) {
let img = ImageReader::new(Cursor::new(img_file.data)) let data_cursor = Cursor::new(img_file.data);
.with_guessed_format() let img = round_image(data_cursor)
.unwrap()
.decode()
.unwrap() .unwrap()
.resize(126, 126, ::image::imageops::FilterType::Lanczos3); .resize(126, 126, ::image::imageops::FilterType::Lanczos3);
let handle = image::Handle::from_rgba( let handle = image::Handle::from_rgba(
@ -221,8 +216,6 @@ fn get_game_icon(game: &PossibleGames) -> Element<Message> {
container(image(handle).content_fit(iced::ContentFit::Contain).height(Length::Fixed(64.0)).filter_method(image::FilterMethod::Linear)) container(image(handle).content_fit(iced::ContentFit::Contain).height(Length::Fixed(64.0)).filter_method(image::FilterMethod::Linear))
.style(move |_| { .style(move |_| {
container::Style { 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), border: border::rounded(20),
..container::Style::default() ..container::Style::default()
} }
@ -233,16 +226,16 @@ fn get_game_icon(game: &PossibleGames) -> Element<Message> {
} }
} }
fn style_container(direction: f32) -> container::Style { fn style_container(direction: f32, use_gradient: bool) -> container::Style {
let angle = rad(direction); let angle = rad(direction);
let gradient: Option<iced::Background> = 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 { container::Style {
text_color: Color::from_rgba8(255, 255, 255, 1.0).into(), text_color: Color::from_rgba8(255, 255, 255, 1.0).into(),
background: Some( background: gradient,
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(),
),
..container::Style::default() ..container::Style::default()
} }
} }
@ -262,12 +255,18 @@ impl Launcher {
*self = Launcher::Loaded(state); *self = Launcher::Loaded(state);
Task::none() Task::none()
}, },
Message::HoverEnter() => {
Task::none()
}
_ => 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,30 +277,27 @@ impl Launcher {
get_game_icon(&PossibleGames::ZenlessZoneZero), get_game_icon(&PossibleGames::ZenlessZoneZero),
get_game_icon(&PossibleGames::HonkaiStarRail), get_game_icon(&PossibleGames::HonkaiStarRail),
get_game_icon(&PossibleGames::GenshinImpact), get_game_icon(&PossibleGames::GenshinImpact),
// text("test").size(25),
// text("test").size(25),
] ]
.spacing(10), .spacing(10),
) )
// .padding(10) .padding(10)
.align_y(Top) .align_y(Top)
.align_x(Center) .align_x(Center)
.width(Length::Fill); .width(Length::Fill)
.style(move |_| style_container(0.0, true));
// let decorations = container(row![ println!("whuh");
// button(content) match self {
// ]); Launcher::Loading => center(text("Loading...").size(50)).into(),
Launcher::Loaded(state) => {
let topbar = container(row![ let topbar = container(
mouse_area(row![
text("Reversed Rooms").size(25), text("Reversed Rooms").size(25),
Space::new(Length::Fill, Length::Fixed(0.0)), 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) .on_press(Message::DragStarted))
.width(Length::Fill) .width(Length::Fill)
.style(move |_| style_container(0.0)) .style(move |_| style_container(0.0, false))
.padding(10); .padding(10);
let bottom_bar = container(row![ let bottom_bar = container(row![
@ -319,7 +315,7 @@ impl Launcher {
})).interaction(iced::mouse::Interaction::Pointer)) })).interaction(iced::mouse::Interaction::Pointer))
]) ])
.width(Length::Fill) .width(Length::Fill)
.style(move |_theme| style_container(180.0)) .style(move |_theme| style_container(180.0, true))
.padding(20); .padding(20);
let user_area: Column<Message, Theme, Renderer> = let user_area: Column<Message, Theme, Renderer> =
@ -327,16 +323,9 @@ impl Launcher {
let content = container(user_area).center(Length::Fill); let content = container(user_area).center(Length::Fill);
println!("whuh"); stack![get_game_background(&state.selected_game), game_selector, content].into()
match self {
Launcher::Loading => loading_message(),
Launcher::Loaded(state) => {
stack![get_game_background(&state.selected_game), content].into()
} }
} }
} }
} }
fn loading_message<'a>() -> Element<'a, Message> {
center(text("Loading...").size(50)).into()
}

58
src/utils/img_utils.rs Normal file
View file

@ -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<std::borrow::Cow<'static, [u8]>>) -> Result<DynamicImage, Box<dyn std::error::Error>> {
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))
}

1
src/utils/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod img_utils;