add topbar dragging! also fix a lot of perf issues with rerenders
This commit is contained in:
parent
574340749b
commit
43d76ca7a0
4 changed files with 128 additions and 69 deletions
25
Cargo.lock
generated
25
Cargo.lock
generated
|
@ -2071,7 +2071,7 @@ dependencies = [
|
|||
"log",
|
||||
"rustc-hash 1.1.0",
|
||||
"spirv",
|
||||
"strum",
|
||||
"strum 0.26.3",
|
||||
"termcolor",
|
||||
"thiserror 2.0.12",
|
||||
"unicode-xid",
|
||||
|
@ -2845,6 +2845,8 @@ dependencies = [
|
|||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum 0.27.1",
|
||||
"strum_macros 0.27.1",
|
||||
"tempfile",
|
||||
"url",
|
||||
]
|
||||
|
@ -3225,9 +3227,15 @@ version = "0.26.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
"strum_macros 0.26.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.4"
|
||||
|
@ -3241,6 +3249,19 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "svg_fmt"
|
||||
version = "0.4.4"
|
||||
|
|
|
@ -15,6 +15,8 @@ iced_video_player = {path = "./iced_video_player"}
|
|||
url = "2.5.4"
|
||||
rust-embed = "8.7.0"
|
||||
file-format = "0.26.0"
|
||||
strum = "0.27.1"
|
||||
strum_macros = "0.27.1"
|
||||
|
||||
[profile.release]
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
|
|
|
@ -10,27 +10,27 @@ use std::{marker::PhantomData, sync::atomic::Ordering};
|
|||
use std::{sync::Arc, time::Instant};
|
||||
|
||||
/// Video player widget which displays the current frame of a [`Video`](crate::Video).
|
||||
pub struct VideoPlayer<Message, Theme = iced::Theme, Renderer = iced::Renderer>
|
||||
pub struct VideoPlayer<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer>
|
||||
where
|
||||
Renderer: PrimitiveRenderer
|
||||
{
|
||||
video: Video,
|
||||
video: &'a Video,
|
||||
content_fit: iced::ContentFit,
|
||||
width: iced::Length,
|
||||
height: iced::Length,
|
||||
on_end_of_stream: Option<Message>,
|
||||
on_new_frame: Option<Message>,
|
||||
on_subtitle_text: Option<Box<dyn Fn(Option<String>) -> Message>>,
|
||||
on_error: Option<Box<dyn Fn(&glib::Error) -> Message>>,
|
||||
on_subtitle_text: Option<Box<dyn Fn(Option<String>) -> Message + 'a>>,
|
||||
on_error: Option<Box<dyn Fn(&glib::Error) -> Message + 'a>>,
|
||||
_phantom: PhantomData<(Theme, Renderer)>,
|
||||
}
|
||||
|
||||
impl<Message, Theme, Renderer> VideoPlayer<Message, Theme, Renderer>
|
||||
impl<'a, Message, Theme, Renderer> VideoPlayer<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: PrimitiveRenderer
|
||||
{
|
||||
/// Creates a new video player widget for a given video.
|
||||
pub fn new(video: Video) -> Self {
|
||||
pub fn new(video: &'a Video) -> Self {
|
||||
VideoPlayer {
|
||||
video,
|
||||
content_fit: iced::ContentFit::default(),
|
||||
|
@ -108,7 +108,7 @@ where
|
|||
}
|
||||
|
||||
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||
for VideoPlayer<Message, Theme, Renderer>
|
||||
for VideoPlayer<'_, Message, Theme, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: PrimitiveRenderer,
|
||||
|
@ -292,14 +292,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<Message, Theme, Renderer> From<VideoPlayer<Message, Theme, Renderer>>
|
||||
for Element<'_, Message, Theme, Renderer>
|
||||
impl<'a, Message, Theme, Renderer> From<VideoPlayer<'a, Message, Theme, Renderer>>
|
||||
for Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Message: 'static + Clone,
|
||||
Theme: 'static,
|
||||
Renderer: 'static + PrimitiveRenderer,
|
||||
Message: 'a + Clone,
|
||||
Theme: 'a,
|
||||
Renderer: 'a + PrimitiveRenderer,
|
||||
{
|
||||
fn from(video_player: VideoPlayer<Message, Theme, Renderer>) -> Self {
|
||||
fn from(video_player: VideoPlayer<'a, Message, Theme, Renderer>) -> Self {
|
||||
Self::new(video_player)
|
||||
}
|
||||
}
|
144
src/main.rs
144
src/main.rs
|
@ -1,19 +1,19 @@
|
|||
mod utils;
|
||||
|
||||
use file_format::FileFormat;
|
||||
use ::image::ImageReader;
|
||||
use ::image::{DynamicImage, 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}, Alignment::Center, Color, Element, Font, Length, Point, Renderer, Size, Subscription, Task, Theme
|
||||
alignment::Vertical::Top, border, gradient, mouse, wgpu::naga::back, 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 strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
use utils::img_utils::round_image;
|
||||
use std::{
|
||||
env, fs::{self, create_dir_all, read_to_string}, io::{Cursor, Read, Write}, path::PathBuf, sync::Arc
|
||||
collections::HashMap, 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"]
|
||||
|
@ -38,7 +38,7 @@ pub fn main() -> iced::Result {
|
|||
max_size: None,
|
||||
min_size: None,
|
||||
visible: true,
|
||||
resizable: true,
|
||||
resizable: false,
|
||||
transparent: false,
|
||||
level: window::Level::Normal,
|
||||
exit_on_close_request: true,
|
||||
|
@ -52,7 +52,7 @@ pub fn main() -> iced::Result {
|
|||
.run()
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, EnumIter, Default, Serialize, Deserialize)]
|
||||
enum PossibleGames {
|
||||
#[default]
|
||||
WutheringWaves,
|
||||
|
@ -64,31 +64,55 @@ enum PossibleGames {
|
|||
#[derive(Debug)]
|
||||
enum Launcher {
|
||||
Loading,
|
||||
Loaded(State),
|
||||
Loaded(Box<State>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug)]
|
||||
enum LauncherBackground {
|
||||
Video(Video),
|
||||
Image(image::Handle),
|
||||
}
|
||||
|
||||
|
||||
impl LauncherBackground {
|
||||
fn inner(&self) -> Element<Message> {
|
||||
match self {
|
||||
LauncherBackground::Video(video) => VideoPlayer::new(video).into(),
|
||||
LauncherBackground::Image(handle) => image(handle).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct State {
|
||||
selected_game: PossibleGames,
|
||||
installed_games: Vec<PossibleGames>,
|
||||
installed_game_servers: Vec<PossibleGames>,
|
||||
db_software_installed: bool,
|
||||
background: Option<LauncherBackground>,
|
||||
icon_images: HashMap<PossibleGames, image::Handle>
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
struct SavedState {
|
||||
installed_games: Vec<PossibleGames>,
|
||||
installed_game_servers: Vec<PossibleGames>,
|
||||
db_software_installed: bool,
|
||||
}
|
||||
|
||||
impl From<SavedState> for Box<State> {
|
||||
fn from(val: SavedState) -> Self {
|
||||
Box::new(State { installed_games: val.installed_games, installed_game_servers: val.installed_game_servers, db_software_installed: val.db_software_installed, ..State::default() })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum LoadError {
|
||||
File,
|
||||
Format,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
enum SaveError {
|
||||
Write,
|
||||
Format,
|
||||
|
@ -96,7 +120,7 @@ enum SaveError {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
Loaded(Result<State, LoadError>),
|
||||
Loaded(Result<SavedState, LoadError>),
|
||||
DragStarted,
|
||||
GameSelected(PossibleGames)
|
||||
}
|
||||
|
@ -151,8 +175,8 @@ fn rad(deg: f32) -> f32 {
|
|||
deg * std::f32::consts::PI / 180.0
|
||||
}
|
||||
|
||||
fn get_game_background(game: &PossibleGames) -> Element<Message> {
|
||||
let file_path: &str = match game {
|
||||
fn get_game_background(state: &State) -> LauncherBackground {
|
||||
let file_path: &str = match state.selected_game {
|
||||
PossibleGames::WutheringWaves => "wutheringwaves-bg.mp4",
|
||||
PossibleGames::ZenlessZoneZero => "zenlesszonezero-bg.png",
|
||||
PossibleGames::HonkaiStarRail => "honkaistarrail-bg.png",
|
||||
|
@ -170,8 +194,7 @@ fn get_game_background(game: &PossibleGames) -> Element<Message> {
|
|||
match Video::new(url::Url::from_file_path(temp_path).unwrap()) {
|
||||
Ok(mut video) => {
|
||||
video.set_looping(true);
|
||||
|
||||
VideoPlayer::new(video).into()
|
||||
LauncherBackground::Video(video)
|
||||
},
|
||||
Err(err) => {
|
||||
panic!("{:#?}", err)
|
||||
|
@ -183,20 +206,19 @@ fn get_game_background(game: &PossibleGames) -> Element<Message> {
|
|||
.unwrap()
|
||||
.decode()
|
||||
.unwrap();
|
||||
let handle = image::Handle::from_rgba(
|
||||
LauncherBackground::Image(image::Handle::from_rgba(
|
||||
img.width(),
|
||||
img.height(),
|
||||
img.to_rgba8().into_raw()
|
||||
);
|
||||
image(handle).content_fit(iced::ContentFit::Fill).into()
|
||||
))
|
||||
}
|
||||
|
||||
} else {
|
||||
panic!("Missing icon for {:?}, path: {}", game, file_path)
|
||||
panic!("Missing icon for {:?}, path: {}", state.selected_game, file_path)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_game_icon(game: &PossibleGames) -> Element<Message> {
|
||||
fn get_game_icon_handle(game: &PossibleGames) -> image::Handle {
|
||||
let file_path: &str = match game {
|
||||
PossibleGames::WutheringWaves => "wutheringwaves-icon.png",
|
||||
PossibleGames::ZenlessZoneZero => "zenlesszonezero-icon.png",
|
||||
|
@ -208,24 +230,22 @@ fn get_game_icon(game: &PossibleGames) -> Element<Message> {
|
|||
let img = round_image(data_cursor)
|
||||
.unwrap()
|
||||
.resize(126, 126, ::image::imageops::FilterType::Lanczos3);
|
||||
let handle = image::Handle::from_rgba(
|
||||
|
||||
image::Handle::from_rgba(
|
||||
img.width(),
|
||||
img.height(),
|
||||
img.to_rgba8().into_raw()
|
||||
);
|
||||
container(image(handle).content_fit(iced::ContentFit::Contain).height(Length::Fixed(64.0)).filter_method(image::FilterMethod::Linear))
|
||||
.style(move |_| {
|
||||
container::Style {
|
||||
border: border::rounded(20),
|
||||
..container::Style::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
)
|
||||
} else {
|
||||
panic!("Missing icon for {:?}, path: {}", game, file_path)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_game_icon<'a>(state: &'a State, game: &'a PossibleGames) -> Element<'a, Message> {
|
||||
let handle = state.icon_images.get(game).unwrap();
|
||||
container(image(handle).content_fit(iced::ContentFit::Contain).height(Length::Fixed(64.0)).filter_method(image::FilterMethod::Linear)).into()
|
||||
}
|
||||
|
||||
fn style_container(direction: f32, use_gradient: bool) -> container::Style {
|
||||
let angle = rad(direction);
|
||||
let gradient: Option<iced::Background> = if use_gradient {
|
||||
|
@ -241,7 +261,18 @@ fn style_container(direction: f32, use_gradient: bool) -> container::Style {
|
|||
}
|
||||
impl Launcher {
|
||||
fn boot() -> (Self, Task<Message>) {
|
||||
(Self::Loaded(State::default()), Task::none())
|
||||
let launcher_bg = get_game_background(&State::default());
|
||||
let mut icons = HashMap::new();
|
||||
for game in PossibleGames::iter() {
|
||||
let icon = get_game_icon_handle(&game);
|
||||
icons.insert(game, icon);
|
||||
}
|
||||
let final_state = State {
|
||||
background: Some(launcher_bg),
|
||||
icon_images: icons,
|
||||
..State::default()
|
||||
};
|
||||
(Self::Loaded(Box::new(final_state)), Task::none())
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
|
@ -251,8 +282,8 @@ impl Launcher {
|
|||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match self {
|
||||
Launcher::Loading => match message {
|
||||
Message::Loaded(Ok(state)) => {
|
||||
*self = Launcher::Loaded(state);
|
||||
Message::Loaded(Ok(save_state)) => {
|
||||
*self = Launcher::Loaded(save_state.into());
|
||||
Task::none()
|
||||
},
|
||||
_ => Task::none(),
|
||||
|
@ -270,26 +301,26 @@ impl Launcher {
|
|||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
let game_selector = container(
|
||||
row![
|
||||
get_game_icon(&PossibleGames::WutheringWaves),
|
||||
get_game_icon(&PossibleGames::ZenlessZoneZero),
|
||||
get_game_icon(&PossibleGames::HonkaiStarRail),
|
||||
get_game_icon(&PossibleGames::GenshinImpact),
|
||||
]
|
||||
.spacing(10),
|
||||
)
|
||||
.padding(10)
|
||||
.align_y(Top)
|
||||
.align_x(Center)
|
||||
.width(Length::Fill)
|
||||
.style(move |_| style_container(0.0, true));
|
||||
|
||||
println!("whuh");
|
||||
fn view(&self) -> Element<Message> {
|
||||
println!("rerender triggered");
|
||||
match self {
|
||||
Launcher::Loading => center(text("Loading...").size(50)).into(),
|
||||
Launcher::Loaded(state) => {
|
||||
let game_selector = container(
|
||||
row![
|
||||
get_game_icon(state, &PossibleGames::WutheringWaves),
|
||||
get_game_icon(state, &PossibleGames::ZenlessZoneZero),
|
||||
get_game_icon(state, &PossibleGames::HonkaiStarRail),
|
||||
get_game_icon(state, &PossibleGames::GenshinImpact),
|
||||
]
|
||||
.spacing(10),
|
||||
)
|
||||
.padding(10)
|
||||
.align_y(Top)
|
||||
.align_x(Center)
|
||||
.width(Length::Fill)
|
||||
.style(move |_| style_container(0.0, true));
|
||||
|
||||
let topbar = container(
|
||||
mouse_area(row![
|
||||
text("Reversed Rooms").size(25),
|
||||
|
@ -322,8 +353,13 @@ impl Launcher {
|
|||
column![topbar, Space::new(Length::Fill, Length::Fill), bottom_bar].width(Length::Fill);
|
||||
|
||||
let content = container(user_area).center(Length::Fill);
|
||||
let background = state.background.as_ref().unwrap();
|
||||
let bg_element: Element<Message> = match background {
|
||||
LauncherBackground::Video(video) => VideoPlayer::new(video).into(),
|
||||
LauncherBackground::Image(handle) => image(handle.clone()).into(),
|
||||
};
|
||||
|
||||
stack![get_game_background(&state.selected_game), game_selector, content].into()
|
||||
stack![bg_element, game_selector, content].into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue