diff --git a/src/engines/mod.rs b/src/engines/mod.rs index 1a25a22..e4504b1 100644 --- a/src/engines/mod.rs +++ b/src/engines/mod.rs @@ -1,20 +1,15 @@ use iced::keyboard; use iced::mouse::{self, Interaction}; use iced::Size; +use std::sync::{Arc, RwLock}; // use iced::widget::image::{Handle, Image}; use iced::Point; +use rand::Rng; use url::Url; #[cfg(feature = "webkit")] pub mod ultralight; -#[derive(Debug, Clone)] -pub struct Tab { - pub url: Url, - pub title: String, - // icon: Image, -} - pub enum PixelFormat { RGBA, BGRA, @@ -22,6 +17,8 @@ pub enum PixelFormat { #[allow(unused)] pub trait BrowserEngine { + type TabInfo: TabInfo; + fn new() -> Self; fn do_work(&self); @@ -37,11 +34,10 @@ pub trait BrowserEngine { fn get_url(&self) -> Option; fn goto_url(&self, url: &Url); fn has_loaded(&self) -> bool; - fn new_tab(&mut self, url: &Url); - fn goto_tab(&mut self, idx: u32) -> Option<()>; - fn get_tabs(&self) -> Vec; - fn current_tab(&self) -> (usize, Tab); - fn close_tab(&mut self, idx: u32); + fn new_tab(&mut self, url: &Url) -> u32; + fn goto_tab(&mut self, id: u32); + fn get_tabs(&self) -> &Tabs; + fn get_tabs_mut(&mut self) -> &mut Tabs; fn refresh(&self); fn go_forward(&self); @@ -53,3 +49,102 @@ pub trait BrowserEngine { fn handle_keyboard_event(&self, event: keyboard::Event); fn handle_mouse_event(&mut self, point: Point, event: mouse::Event); } + +/// Engine specific tab information +pub trait TabInfo {} + +/// Stores Tab info like url & title +// Some browser engines take a closure to the url and title +// to automatically update it when it changes +pub struct Tab { + id: u32, + url: Arc>, + title: Arc>, + tab_info: TabInfo, +} + +impl Tab { + pub fn new(url: Arc>, title: Arc>, tab_info: TabInfo) -> Self { + let id = rand::thread_rng().gen(); + Self { + id, + url, + title, + tab_info, + } + } + + pub fn id(&self) -> u32 { + self.id + } + + pub fn url(&self) -> String { + self.url.read().unwrap().to_string() + } + + pub fn title(&self) -> String { + self.title.read().unwrap().to_string() + } +} + +pub struct Tabs { + tabs: Vec>, + current: u32, +} + +impl Tabs { + pub fn new() -> Self { + Self { + tabs: Vec::new(), + current: 0, + } + } + + pub fn get_current_id(&self) -> u32 { + self.current + } + + pub fn set_current_id(&mut self, id: u32) { + self.current = id + } + + pub fn tabs(&self) -> &Vec> { + &self.tabs + } + + pub fn insert(&mut self, tab: Tab) -> u32 { + let id = tab.id; + self.tabs.push(tab); + id + } + + pub fn remove(&mut self, id: u32) { + self.tabs.retain(|tab| tab.id != id) + } + + pub fn get_current(&self) -> &Tab { + self.get(self.current) + } + + pub fn get_current_mut(&mut self) -> &mut Tab { + self.get_mut(self.current) + } + + pub fn get(&self, id: u32) -> &Tab { + for tab in self.tabs.iter() { + if tab.id == id { + return tab; + } + } + panic!("Unable to find Tab with id: {}", id); + } + + pub fn get_mut(&mut self, id: u32) -> &mut Tab { + for tab in self.tabs.iter_mut() { + if tab.id == id { + return tab; + } + } + panic!("Unable to find Tab with id: {}", id); + } +} diff --git a/src/engines/ultralight.rs b/src/engines/ultralight.rs index 2212f59..6a20536 100644 --- a/src/engines/ultralight.rs +++ b/src/engines/ultralight.rs @@ -18,7 +18,7 @@ use url::Url; #[cfg(not(debug_assertions))] use env_home::env_home_dir; -use super::{BrowserEngine, PixelFormat}; +use super::{BrowserEngine, PixelFormat, Tab, TabInfo, Tabs}; struct UlLogger; impl Logger for UlLogger { @@ -27,30 +27,20 @@ impl Logger for UlLogger { } } -pub struct Tab { +pub struct UltalightTabInfo { surface: Surface, view: View, - title: Arc>, - url: Arc>, cursor: Arc>, } -impl From<&Tab> for super::Tab { - fn from(value: &Tab) -> Self { - Self { - url: Url::parse(value.url.read().unwrap().as_str()).unwrap(), - title: value.title.read().unwrap().to_string(), - } - } -} +impl TabInfo for UltalightTabInfo {} pub struct Ultralight { renderer: Renderer, view_config: ViewConfig, width: u32, height: u32, - current_tab: Option, - tabs: Vec, + tabs: Tabs, } impl Ultralight { @@ -91,29 +81,14 @@ impl Ultralight { view_config, width: 800, height: 800, - current_tab: None, - tabs: Vec::new(), - } - } - - fn get_tab(&self) -> Option<&Tab> { - if let Some(index) = self.current_tab.as_ref() { - self.tabs.get(*index as usize) - } else { - None - } - } - - fn get_tab_mut(&mut self) -> Option<&mut Tab> { - if let Some(index) = self.current_tab.as_ref() { - self.tabs.get_mut(*index as usize) - } else { - None + tabs: Tabs::new(), } } } impl BrowserEngine for Ultralight { + type TabInfo = UltalightTabInfo; + fn new() -> Self { Ultralight::new() } @@ -123,7 +98,7 @@ impl BrowserEngine for Ultralight { } fn need_render(&self) -> bool { - self.get_tab().unwrap().view.needs_paint() + self.get_tabs().get_current().tab_info.view.needs_paint() } fn render(&mut self) { @@ -137,9 +112,9 @@ impl BrowserEngine for Ultralight { fn resize(&mut self, size: Size) { let (width, height) = (size.width as u32, size.height as u32); (self.width, self.height) = (width, height); - self.tabs.iter().for_each(|tab| { - tab.view.resize(width, height); - tab.surface.resize(width, height); + self.tabs.tabs.iter().for_each(|tab| { + tab.tab_info.view.resize(width, height); + tab.tab_info.surface.resize(width, height); }) } @@ -148,7 +123,7 @@ impl BrowserEngine for Ultralight { let size = self.size(); let mut vec = Vec::new(); - match self.get_tab_mut().unwrap().surface.lock_pixels() { + match self.tabs.get_current_mut().tab_info.surface.lock_pixels() { Some(pixel_data) => vec.extend_from_slice(&pixel_data), None => { let image = vec![255; size.0 as usize * size.1 as usize]; @@ -160,37 +135,41 @@ impl BrowserEngine for Ultralight { } fn get_cursor(&self) -> mouse::Interaction { - *self.get_tab().unwrap().cursor.read().unwrap() + *self.tabs.get_current().tab_info.cursor.read().unwrap() } fn get_title(&self) -> Option { - self.get_tab()?.view.title().ok() + self.tabs.get_current().tab_info.view.title().ok() } fn get_url(&self) -> Option { - Some(Url::parse(self.get_tab()?.url.read().ok()?.clone().as_str()).ok()?) + Some(Url::parse(self.tabs.get_current().tab_info.view.url().ok()?.as_str()).ok()?) } fn goto_url(&self, url: &Url) { - self.get_tab().unwrap().view.load_url(url.as_ref()).unwrap(); + self.tabs + .get_current() + .tab_info + .view + .load_url(url.as_ref()) + .unwrap(); } fn has_loaded(&self) -> bool { - !self.get_tab().unwrap().view.is_loading() + !self.tabs.get_current().tab_info.view.is_loading() } - fn get_tabs(&self) -> Vec { - self.tabs.iter().map(Into::::into).collect() + fn get_tabs(&self) -> &Tabs { + &self.tabs } - fn current_tab(&self) -> (usize, super::Tab) { - let idx = self.current_tab.unwrap() as usize; - let tab = self.tabs.get(idx).unwrap(); - (idx, tab.into()) + fn get_tabs_mut(&mut self) -> &mut Tabs { + &mut self.tabs } - fn new_tab(&mut self, url: &Url) { + fn new_tab(&mut self, url: &Url) -> u32 { if self + .tabs .tabs .iter() .filter(|tab| *tab.url.read().unwrap() == *url.as_ref()) @@ -243,47 +222,43 @@ impl BrowserEngine for Ultralight { }; }); - let tab = Tab { - title, - view, + let tab_info = UltalightTabInfo { surface, - url: site_url, + view, cursor, }; - self.tabs.push(tab); - self.current_tab = Some(self.tabs.len() as u32 - 1); - } - } + let tab = Tab::new(site_url, title, tab_info); + let id = tab.id; - fn goto_tab(&mut self, idx: u32) -> Option<()> { - assert!(self.tabs.len() as u32 >= idx); - self.current_tab = Some(idx); - Some(()) + self.tabs.insert(tab); + return id; + } + unreachable!(); } - fn close_tab(&mut self, idx: u32) { - self.tabs.remove(idx as usize); + fn goto_tab(&mut self, id: u32) { + self.tabs.current = id } fn refresh(&self) { - self.get_tab().unwrap().view.reload(); + self.tabs.get_current().tab_info.view.reload(); } fn go_forward(&self) { - self.get_tab().unwrap().view.go_forward(); + self.tabs.get_current().tab_info.view.go_forward(); } fn go_back(&self) { - self.get_tab().unwrap().view.go_back(); + self.tabs.get_current().tab_info.view.go_back(); } fn focus(&self) { - self.get_tab().unwrap().view.focus(); + self.tabs.get_current().tab_info.view.focus(); } fn unfocus(&self) { - self.get_tab().unwrap().view.unfocus(); + self.tabs.get_current().tab_info.view.unfocus(); } fn scroll(&self, delta: ScrollDelta) { @@ -301,7 +276,11 @@ impl BrowserEngine for Ultralight { ) .unwrap(), }; - self.get_tab().unwrap().view.fire_scroll_event(scroll_event); + self.tabs + .get_current() + .tab_info + .view + .fire_scroll_event(scroll_event); } fn handle_keyboard_event(&self, event: keyboard::Event) { @@ -335,7 +314,11 @@ impl BrowserEngine for Ultralight { }; if let Some(key_event) = key_event { - self.get_tab().unwrap().view.fire_key_event(key_event); + self.tabs + .get_current() + .tab_info + .view + .fire_key_event(key_event); } } @@ -350,7 +333,7 @@ impl BrowserEngine for Ultralight { mouse::Event::ButtonPressed(mouse::Button::Back) => (), mouse::Event::ButtonReleased(mouse::Button::Back) => (), mouse::Event::ButtonPressed(mouse::Button::Left) => { - self.get_tab().unwrap().view.fire_mouse_event( + self.tabs.get_current().tab_info.view.fire_mouse_event( MouseEvent::new( ul_next::event::MouseEventType::MouseDown, point.x as i32, @@ -361,7 +344,7 @@ impl BrowserEngine for Ultralight { ); } mouse::Event::ButtonReleased(mouse::Button::Left) => { - self.get_tab().unwrap().view.fire_mouse_event( + self.tabs.get_current().tab_info.view.fire_mouse_event( MouseEvent::new( ul_next::event::MouseEventType::MouseUp, point.x as i32, @@ -372,7 +355,7 @@ impl BrowserEngine for Ultralight { ); } mouse::Event::ButtonPressed(mouse::Button::Right) => { - self.get_tab().unwrap().view.fire_mouse_event( + self.tabs.get_current().tab_info.view.fire_mouse_event( MouseEvent::new( ul_next::event::MouseEventType::MouseDown, point.x as i32, @@ -383,7 +366,7 @@ impl BrowserEngine for Ultralight { ); } mouse::Event::ButtonReleased(mouse::Button::Right) => { - self.get_tab().unwrap().view.fire_mouse_event( + self.tabs.get_current().tab_info.view.fire_mouse_event( MouseEvent::new( ul_next::event::MouseEventType::MouseUp, point.x as i32, @@ -394,7 +377,7 @@ impl BrowserEngine for Ultralight { ); } mouse::Event::CursorMoved { position: _ } => { - self.get_tab().unwrap().view.fire_mouse_event( + self.tabs.get_current().tab_info.view.fire_mouse_event( MouseEvent::new( ul_next::event::MouseEventType::MouseMoved, point.x as i32, diff --git a/src/lib.rs b/src/lib.rs index d25df0f..4c55d4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ use iced::widget::image::{Handle, Image}; use url::{ParseError, Url}; mod engines; -pub use engines::BrowserEngine; +pub use engines::{BrowserEngine, Tab, TabInfo, Tabs}; #[cfg(feature = "ultralight")] pub use engines::ultralight::Ultralight; diff --git a/src/widgets/browser_widgets.rs b/src/widgets/browser_widgets.rs index 0fc7f19..5d65c0c 100644 --- a/src/widgets/browser_widgets.rs +++ b/src/widgets/browser_widgets.rs @@ -17,8 +17,8 @@ pub enum Message { Refresh, GoHome, GoUrl(String), - ChangeTab(usize), - CloseTab(usize), + ChangeTab(u32), + CloseTab(u32), CreateTab, UrlChanged(String), SendKeyboardEvent(keyboard::Event), @@ -38,7 +38,10 @@ pub struct BrowserWidget { view_bounds: Size, } -impl Default for BrowserWidget { +impl Default for BrowserWidget +where + Engine: BrowserEngine, +{ fn default() -> Self { let home = Url::parse(Self::HOME).unwrap(); Self { @@ -64,7 +67,10 @@ impl BrowserWidget { } } -impl BrowserWidget { +impl BrowserWidget +where + Engine: BrowserEngine, +{ const HOME: &'static str = "https://duckduckgo.com"; pub fn new() -> Self { @@ -136,14 +142,15 @@ impl BrowserWidget { Message::SendMouseEvent(point, event) => { self.engine_mut().handle_mouse_event(point, event); } - Message::ChangeTab(index) => self.engine_mut().goto_tab(index as u32).unwrap(), - Message::CloseTab(index) => { - self.engine_mut().close_tab(index as u32); + Message::ChangeTab(id) => self.engine_mut().goto_tab(id), + Message::CloseTab(id) => { + self.engine_mut().get_tabs_mut().remove(id); } Message::CreateTab => { self.url = self.home.to_string(); let home = self.home.clone(); - self.engine_mut().new_tab(&home); + let id = self.engine_mut().new_tab(&home); + self.engine_mut().get_tabs_mut().set_current_id(id); } Message::GoBackward => { self.engine().go_back(); @@ -185,7 +192,7 @@ impl BrowserWidget { if self.tab_bar { let tabs = self.engine().get_tabs(); - let (active_tab, _) = self.engine().current_tab(); + let active_tab = self.engine().get_tabs().get_current().id(); column = column.push(tab_bar(tabs, active_tab)) } if self.nav_bar { diff --git a/src/widgets/tab_bar.rs b/src/widgets/tab_bar.rs index bd44fac..43a833c 100644 --- a/src/widgets/tab_bar.rs +++ b/src/widgets/tab_bar.rs @@ -4,15 +4,16 @@ use iced_aw::core::icons::bootstrap::{icon_to_text, Bootstrap}; use iced_aw::{TabBar as TB, TabLabel}; use super::browser_widgets::Message; -use crate::engines::Tab; +use crate::engines::Tabs; // helper function to create navigation bar -pub fn tab_bar(tabs: Vec, active_tab: usize) -> Element<'static, Message> { +pub fn tab_bar(tabs: &Tabs, active_tab: u32) -> Element<'static, Message> { let tab_bar = tabs + .tabs() .iter() .fold(TB::new(Message::ChangeTab), |tab_bar, tab| { - let idx = tab_bar.size(); - tab_bar.push(idx, TabLabel::Text(tab.title.to_owned())) + let id = tab_bar.size(); + tab_bar.push(id as u32, TabLabel::Text(tab.title())) }) .set_active_tab(&active_tab) .on_close(Message::CloseTab)