desktop: add UI for bookmarks

This commit is contained in:
sleepycatcoding 2024-03-24 21:57:49 +02:00 committed by Nathan Adams
parent 0b7b9eb0a1
commit e1f9b5e5df
8 changed files with 150 additions and 11 deletions

View File

@ -530,6 +530,10 @@ impl Player {
self.audio.set_volume(volume)
}
pub fn swf(&self) -> Arc<SwfMovie> {
self.swf.clone()
}
pub fn prepare_context_menu(&mut self) -> Vec<ContextMenuItem> {
self.mutate_with_update_context(|context| {
if !context.stage.show_menu() {

View File

@ -0,0 +1 @@
bookmarks-dialog = Manage Bookmarks

View File

@ -2,4 +2,5 @@ language-name = English (United States)
start = Start
browse = Browse
save = Save
save = Save
remove = Remove

View File

@ -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

View File

@ -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<ContextMenu>,
open_dialog: OpenDialog,
preferences_dialog: Option<PreferencesDialog>,
bookmarks_dialog: Option<BookmarksDialog>,
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();

View File

@ -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
}
}

View File

@ -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()));

View File

@ -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<Url, Error> {
}
}
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<PathBuf>) -> Option<PathBuf> {
let mut dialog = FileDialog::new()
.add_filter("Flash Files", &["swf", "spl"])