From 1c06622dbcaefceec2e1cad32cad93eb54e6626b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 8 Mar 2021 17:48:23 +0100 Subject: [PATCH] Hold down a modifier key when clicking a link to open it in a new tab --- egui/src/data/input.rs | 10 +++++++++ egui/src/data/output.rs | 39 +++++++++++++++++++++++++++++++---- egui/src/lib.rs | 5 ++++- egui/src/widgets/hyperlink.rs | 6 +++++- egui_demo_lib/src/wrap_app.rs | 6 +++--- egui_glium/src/lib.rs | 4 ++-- egui_web/CHANGELOG.md | 4 ++++ egui_web/src/lib.rs | 10 +++++---- 8 files changed, 69 insertions(+), 15 deletions(-) diff --git a/egui/src/data/input.rs b/egui/src/data/input.rs index 898c9c0e..61d9c3f9 100644 --- a/egui/src/data/input.rs +++ b/egui/src/data/input.rs @@ -152,6 +152,16 @@ pub struct Modifiers { pub command: bool, } +impl Modifiers { + pub fn is_none(&self) -> bool { + self == &Self::default() + } + + pub fn any(&self) -> bool { + !self.is_none() + } +} + /// Keyboard keys. /// /// Includes all keys egui is interested in (such as `Home` and `End`) diff --git a/egui/src/data/output.rs b/egui/src/data/output.rs index 143a685e..7079473d 100644 --- a/egui/src/data/output.rs +++ b/egui/src/data/output.rs @@ -2,13 +2,13 @@ /// What egui emits each frame. /// The backend should use this. -#[derive(Clone, Default)] +#[derive(Clone, Default, PartialEq)] pub struct Output { /// Set the cursor to this icon. pub cursor_icon: CursorIcon, /// If set, open this url. - pub open_url: Option, + pub open_url: Option, /// Response to [`crate::Event::Copy`] or [`crate::Event::Cut`]. Ignore if empty. pub copied_text: String, @@ -25,10 +25,35 @@ pub struct Output { pub events: Vec, } +#[derive(Clone, PartialEq)] +pub struct OpenUrl { + pub url: String, + /// If `true`, open the url in a new tab. + /// If `false` open it in the same tab. + /// Only matters when in a web browser. + pub new_tab: bool, +} + +impl OpenUrl { + pub fn same_tab(url: impl Into) -> Self { + Self { + url: url.into(), + new_tab: false, + } + } + + pub fn new_tab(url: impl Into) -> Self { + Self { + url: url.into(), + new_tab: true, + } + } +} + /// A mouse cursor icon. /// /// egui emits a [`CursorIcon`] in [`Output`] each frame as a request to the integration. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] pub enum CursorIcon { Default, /// Pointing hand, used for e.g. web links @@ -52,7 +77,7 @@ impl Default for CursorIcon { /// Things that happened during this frame that the integration may be interested in. /// /// In particular, these events may be useful for accessability, i.e. for screen readers. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum OutputEvent { /// A widget gained keyboard focus (by tab key). /// @@ -81,6 +106,12 @@ pub enum WidgetType { } impl Output { + /// Open the given url in a web browser. + /// If egui is running in a browser, the same tab will be reused. + pub fn open_url(&mut self, url: impl Into) { + self.open_url = Some(OpenUrl::new_tab(url)) + } + /// Inform the backend integration that a widget gained focus pub fn push_gained_focus_event(&mut self, widget_type: WidgetType, text: impl Into) { self.events diff --git a/egui/src/lib.rs b/egui/src/lib.rs index 95875d1f..7d2109e5 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -307,7 +307,10 @@ pub use epaint::{ pub use { containers::*, context::{Context, CtxRef}, - data::{input::*, output::*}, + data::{ + input::*, + output::{self, CursorIcon, Output, WidgetType}, + }, grid::Grid, id::Id, input_state::{InputState, PointerState}, diff --git a/egui/src/widgets/hyperlink.rs b/egui/src/widgets/hyperlink.rs index a0283a65..ac32dbff 100644 --- a/egui/src/widgets/hyperlink.rs +++ b/egui/src/widgets/hyperlink.rs @@ -62,7 +62,11 @@ impl Widget for Hyperlink { ui.ctx().output().cursor_icon = CursorIcon::PointingHand; } if response.clicked() { - ui.ctx().output().open_url = Some(url.clone()); + let modifiers = ui.ctx().input().modifiers; + ui.ctx().output().open_url = Some(crate::output::OpenUrl { + url: url.clone(), + new_tab: modifiers.any(), + }); } let color = ui.visuals().hyperlink_color; diff --git a/egui_demo_lib/src/wrap_app.rs b/egui_demo_lib/src/wrap_app.rs index 4334eb7b..3cec0c55 100644 --- a/egui_demo_lib/src/wrap_app.rs +++ b/egui_demo_lib/src/wrap_app.rs @@ -93,7 +93,7 @@ impl epi::App for WrapApp { { self.selected_anchor = anchor.to_owned(); if frame.is_web() { - ui.output().open_url = Some(format!("#{}", anchor)); + ui.output().open_url(format!("#{}", anchor)); } } } @@ -105,7 +105,7 @@ impl epi::App for WrapApp { if clock_button(ui, seconds_since_midnight).clicked() { self.selected_anchor = "clock".to_owned(); if frame.is_web() { - ui.output().open_url = Some("#clock".to_owned()); + ui.output().open_url("#clock"); } } } @@ -221,7 +221,7 @@ struct BackendPanel { frame_history: crate::frame_history::FrameHistory, #[cfg_attr(feature = "persistence", serde(skip))] - output_event_history: std::collections::VecDeque, + output_event_history: std::collections::VecDeque, } impl Default for BackendPanel { diff --git a/egui_glium/src/lib.rs b/egui_glium/src/lib.rs index 482659f9..b8713d08 100644 --- a/egui_glium/src/lib.rs +++ b/egui_glium/src/lib.rs @@ -281,8 +281,8 @@ pub fn handle_output( display: &glium::backend::glutin::Display, clipboard: Option<&mut ClipboardContext>, ) { - if let Some(url) = output.open_url { - if let Err(err) = webbrowser::open(&url) { + if let Some(open) = output.open_url { + if let Err(err) = webbrowser::open(&open.url) { eprintln!("Failed to open url: {}", err); } } diff --git a/egui_web/CHANGELOG.md b/egui_web/CHANGELOG.md index c1b43d69..ff041150 100644 --- a/egui_web/CHANGELOG.md +++ b/egui_web/CHANGELOG.md @@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased +### Added ⭐ + +* Hold down a modifier key when clicking a link to open it in a new tab. + ## 0.10.0 - 2021-02-28 diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index 613fa839..42b6f151 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -237,8 +237,8 @@ pub fn handle_output(output: &egui::Output) { } = output; set_cursor_icon(*cursor_icon); - if let Some(url) = open_url { - crate::open_url(url); + if let Some(open) = open_url { + crate::open_url(&open.url, open.new_tab); } #[cfg(web_sys_unstable_apis)] @@ -299,9 +299,11 @@ fn cursor_web_name(cursor: egui::CursorIcon) -> &'static str { } } -pub fn open_url(url: &str) -> Option<()> { +pub fn open_url(url: &str, new_tab: bool) -> Option<()> { + let name = if new_tab { "_blank" } else { "_self" }; + web_sys::window()? - .open_with_url_and_target(url, "_self") + .open_with_url_and_target(url, name) .ok()?; Some(()) }