From e1f9b5e5df2babe9a1eec8ddfc675953007a5993 Mon Sep 17 00:00:00 2001 From: sleepycatcoding <131554884+sleepycatcoding@users.noreply.github.com> Date: Sun, 24 Mar 2024 21:57:49 +0200 Subject: [PATCH] desktop: add UI for bookmarks --- core/src/player.rs | 4 ++ .../assets/texts/en-US/bookmarks_dialog.ftl | 1 + desktop/assets/texts/en-US/common.ftl | 3 +- desktop/assets/texts/en-US/main_menu.ftl | 4 ++ desktop/src/gui.rs | 63 ++++++++++++++++++ desktop/src/gui/bookmarks_dialog.rs | 64 +++++++++++++++++++ desktop/src/player.rs | 12 +--- desktop/src/util.rs | 10 +++ 8 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 desktop/assets/texts/en-US/bookmarks_dialog.ftl create mode 100644 desktop/src/gui/bookmarks_dialog.rs diff --git a/core/src/player.rs b/core/src/player.rs index 0b0313a55..d90c35fae 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -530,6 +530,10 @@ impl Player { self.audio.set_volume(volume) } + pub fn swf(&self) -> Arc { + self.swf.clone() + } + pub fn prepare_context_menu(&mut self) -> Vec { self.mutate_with_update_context(|context| { if !context.stage.show_menu() { diff --git a/desktop/assets/texts/en-US/bookmarks_dialog.ftl b/desktop/assets/texts/en-US/bookmarks_dialog.ftl new file mode 100644 index 000000000..d631875fe --- /dev/null +++ b/desktop/assets/texts/en-US/bookmarks_dialog.ftl @@ -0,0 +1 @@ +bookmarks-dialog = Manage Bookmarks \ No newline at end of file diff --git a/desktop/assets/texts/en-US/common.ftl b/desktop/assets/texts/en-US/common.ftl index 23db63a68..5f15d49b1 100644 --- a/desktop/assets/texts/en-US/common.ftl +++ b/desktop/assets/texts/en-US/common.ftl @@ -2,4 +2,5 @@ language-name = English (United States) start = Start browse = Browse -save = Save \ No newline at end of file +save = Save +remove = Remove \ No newline at end of file diff --git a/desktop/assets/texts/en-US/main_menu.ftl b/desktop/assets/texts/en-US/main_menu.ftl index a8505bbc1..e74278089 100644 --- a/desktop/assets/texts/en-US/main_menu.ftl +++ b/desktop/assets/texts/en-US/main_menu.ftl @@ -27,6 +27,10 @@ help-menu-sponsor-development = Sponsor Development... help-menu-translate-ruffle = Translate Ruffle... help-menu-about = About Ruffle +bookmarks-menu = Bookmarks +bookmarks-menu-add = Add +bookmarks-menu-manage = Manage Bookmarks... + debug-menu = Debug Tools debug-menu-open-stage = View Stage Info debug-menu-open-movie = View Movie diff --git a/desktop/src/gui.rs b/desktop/src/gui.rs index 17e53d318..ae3591c4e 100644 --- a/desktop/src/gui.rs +++ b/desktop/src/gui.rs @@ -1,3 +1,4 @@ +mod bookmarks_dialog; mod context_menu; mod controller; mod movie; @@ -7,9 +8,11 @@ mod preferences_dialog; pub use controller::GuiController; pub use movie::MovieView; use std::borrow::Cow; +use std::str::FromStr; use url::Url; use crate::custom_event::RuffleEvent; +use crate::gui::bookmarks_dialog::BookmarksDialog; use crate::gui::context_menu::ContextMenu; use crate::gui::open_dialog::OpenDialog; use crate::gui::preferences_dialog::PreferencesDialog; @@ -87,6 +90,7 @@ pub struct RuffleGui { context_menu: Option, open_dialog: OpenDialog, preferences_dialog: Option, + bookmarks_dialog: Option, default_player_options: PlayerOptions, currently_opened: Option<(Url, PlayerOptions)>, was_suspended_before_debug: bool, @@ -114,6 +118,7 @@ impl RuffleGui { event_loop.clone(), ), preferences_dialog: None, + bookmarks_dialog: None, event_loop, default_player_options, @@ -139,6 +144,7 @@ impl RuffleGui { self.about_window(&locale, egui_ctx); self.open_dialog(&locale, egui_ctx); self.preferences_dialog(&locale, egui_ctx); + self.bookmarks_dialog(&locale, egui_ctx); if let Some(player) = player { let was_suspended = player.debug_ui().should_suspend_player(); @@ -300,6 +306,48 @@ impl RuffleGui { self.show_volume_screen(ui); } }); + menu::menu_button(ui, text(locale, "bookmarks-menu"), |ui| { + ui.add_enabled_ui(player.is_some(), |ui| { + if Button::new(text(locale, "bookmarks-menu-add")).ui(ui).clicked() { + ui.close_menu(); + if let Some(player) = &player { + if let Err(e) = self.preferences.write_bookmarks(|writer| { + // FIXME: if spoof url is used, the URL here is incorrect (fun fact, its also incorrect in the debug tools). + match Url::from_str(player.swf().url()) { + Ok(url) => writer.add(crate::preferences::Bookmark { + url, + }), + Err(e) => tracing::warn!("Failed to parse SWF url for bookmark: {e}"), + }; + }) { + tracing::warn!("Couldn't update bookmarks: {e}"); + } + } + } + }); + + let mut have_bookmarks = false; + + self.preferences.bookmarks(|bookmarks| { + have_bookmarks = !bookmarks.is_empty(); + + if have_bookmarks { + ui.separator(); + for bookmark in bookmarks { + if Button::new(crate::util::url_to_readable_name(&bookmark.url)).ui(ui).clicked() { + ui.close_menu(); + let _ = self.event_loop.send_event(RuffleEvent::OpenURL(bookmark.url.clone(), Box::new(self.default_player_options.clone()))); + } + } + ui.separator(); + } + }); + + if have_bookmarks && Button::new(text(locale, "bookmarks-menu-manage")).ui(ui).clicked() { + ui.close_menu(); + self.open_bookmarks(); + } + }); menu::menu_button(ui, text(locale, "debug-menu"), |ui| { ui.add_enabled_ui(player.is_some(), |ui| { if Button::new(text(locale, "debug-menu-open-stage")).ui(ui).clicked() { @@ -512,6 +560,10 @@ impl RuffleGui { self.preferences_dialog = Some(PreferencesDialog::new(self.preferences.clone())); } + fn open_bookmarks(&mut self) { + self.bookmarks_dialog = Some(BookmarksDialog::new(self.preferences.clone())); + } + fn close_movie(&mut self, ui: &mut egui::Ui) { let _ = self.event_loop.send_event(RuffleEvent::CloseFile); self.currently_opened = None; @@ -546,6 +598,17 @@ impl RuffleGui { } } + fn bookmarks_dialog(&mut self, locale: &LanguageIdentifier, egui_ctx: &egui::Context) { + let keep_open = if let Some(dialog) = &mut self.bookmarks_dialog { + dialog.show(locale, egui_ctx) + } else { + true + }; + if !keep_open { + self.bookmarks_dialog = None; + } + } + fn request_exit(&mut self, ui: &mut egui::Ui) { let _ = self.event_loop.send_event(RuffleEvent::ExitRequested); ui.close_menu(); diff --git a/desktop/src/gui/bookmarks_dialog.rs b/desktop/src/gui/bookmarks_dialog.rs new file mode 100644 index 000000000..69f34d75c --- /dev/null +++ b/desktop/src/gui/bookmarks_dialog.rs @@ -0,0 +1,64 @@ +use crate::gui::text; +use crate::preferences::GlobalPreferences; +use egui::{Align2, Button, Grid, Widget, Window}; +use unic_langid::LanguageIdentifier; + +pub struct BookmarksDialog { + preferences: GlobalPreferences, +} + +impl BookmarksDialog { + pub fn new(preferences: GlobalPreferences) -> Self { + Self { preferences } + } + + pub fn show(&mut self, locale: &LanguageIdentifier, egui_ctx: &egui::Context) -> bool { + let mut keep_open = true; + let mut should_close = false; + + Window::new(text(locale, "bookmarks-dialog")) + .open(&mut keep_open) + .anchor(Align2::CENTER_CENTER, egui::Vec2::ZERO) + .collapsible(false) + .resizable(false) + .show(egui_ctx, |ui| { + Grid::new("bookmarks-dialog-grid") + .num_columns(2) + .striped(true) + .show(ui, |ui| { + enum BookmarkAction { + Remove(usize), + } + + let mut action = None; + + self.preferences.bookmarks(|bookmarks| { + // Close the dialog if we have no bookmarks to show. + should_close = bookmarks.is_empty(); + + for (index, bookmark) in bookmarks.iter().enumerate() { + ui.label(bookmark.url.as_str()); + + if Button::new(text(locale, "remove")).ui(ui).clicked() { + action = Some(BookmarkAction::Remove(index)); + } + + ui.end_row(); + } + }); + + if let Some(action) = action { + if let Err(e) = + self.preferences.write_bookmarks(|writer| match action { + BookmarkAction::Remove(index) => writer.remove(index), + }) + { + tracing::warn!("Couldn't update bookmarks: {e}"); + } + } + }); + }); + + keep_open && !should_close + } +} diff --git a/desktop/src/player.rs b/desktop/src/player.rs index 4ca37134e..f581ad0c7 100644 --- a/desktop/src/player.rs +++ b/desktop/src/player.rs @@ -19,14 +19,12 @@ use ruffle_render::backend::RenderBackend; use ruffle_render::quality::StageQuality; use ruffle_render_wgpu::backend::WgpuRenderBackend; use ruffle_render_wgpu::descriptors::Descriptors; -use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use std::rc::Rc; use std::sync::{Arc, Mutex, MutexGuard}; use std::time::Duration; use url::Url; -use urlencoding::decode; use winit::event_loop::EventLoopProxy; use winit::window::Window; @@ -191,17 +189,11 @@ impl ActivePlayer { .with_avm2_optimizer_enabled(opt.avm2_optimizer_enabled); let player = builder.build(); - let name = movie_url - .path_segments() - .and_then(|segments| segments.last()) - .unwrap_or_else(|| movie_url.as_str()) - .to_string(); - - let readable_name = decode(&name).unwrap_or(Cow::Borrowed(&name)); + let readable_name = crate::util::url_to_readable_name(movie_url); window.set_title(&format!("Ruffle - {readable_name}")); - SWF_INFO.with(|i| *i.borrow_mut() = Some(name.clone())); + SWF_INFO.with(|i| *i.borrow_mut() = Some(readable_name.into_owned())); let on_metadata = move |swf_header: &ruffle_core::swf::HeaderExt| { let _ = event_loop.send_event(RuffleEvent::OnMetadata(swf_header.clone())); diff --git a/desktop/src/util.rs b/desktop/src/util.rs index 86e257c44..ac43a5d62 100644 --- a/desktop/src/util.rs +++ b/desktop/src/util.rs @@ -3,6 +3,7 @@ use anyhow::{anyhow, Error}; use gilrs::Button; use rfd::FileDialog; use ruffle_core::events::{GamepadButton, KeyCode, TextControlCode}; +use std::borrow::Cow; use std::path::{Path, PathBuf}; use url::Url; use winit::dpi::PhysicalSize; @@ -232,6 +233,15 @@ pub fn parse_url(path: &Path) -> Result { } } +pub fn url_to_readable_name(url: &Url) -> Cow<'_, str> { + let name = url + .path_segments() + .and_then(|segments| segments.last()) + .unwrap_or_else(|| url.as_str()); + + urlencoding::decode(name).unwrap_or(Cow::Borrowed(name)) +} + fn actually_pick_file(dir: Option) -> Option { let mut dialog = FileDialog::new() .add_filter("Flash Files", &["swf", "spl"])