From 2f6a45fe358ec93241d3f98693630245529702f9 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sat, 17 Aug 2024 16:06:08 -0600 Subject: [PATCH 1/2] working on better tabs --- src/engines/mod.rs | 105 +++++++++++++++++++++++---- src/engines/ultralight.rs | 126 +++++++++++++-------------------- src/lib.rs | 2 +- src/widgets/browser_widgets.rs | 10 +-- src/widgets/tab_bar.rs | 4 +- 5 files changed, 152 insertions(+), 95 deletions(-) diff --git a/src/engines/mod.rs b/src/engines/mod.rs index 1a25a22..7da5554 100644 --- a/src/engines/mod.rs +++ b/src/engines/mod.rs @@ -1,27 +1,22 @@ 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, } #[allow(unused)] -pub trait BrowserEngine { +pub trait BrowserEngine { fn new() -> Self; fn do_work(&self); @@ -38,10 +33,9 @@ pub trait BrowserEngine { 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 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 +47,90 @@ 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 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..70512e6 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,12 @@ 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 { +impl BrowserEngine for Ultralight { fn new() -> Self { Ultralight::new() } @@ -123,7 +96,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 +110,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 +121,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 +133,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) { if self + .tabs .tabs .iter() .filter(|tab| *tab.url.read().unwrap() == *url.as_ref()) @@ -243,47 +220,38 @@ 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); + self.tabs.insert(Tab::new(site_url, title, tab_info)); } } - fn goto_tab(&mut self, idx: u32) -> Option<()> { - assert!(self.tabs.len() as u32 >= idx); - self.current_tab = Some(idx); - Some(()) - } - - 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 +269,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 +307,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 +326,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 +337,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 +348,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 +359,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 +370,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..0463e9f 100644 --- a/src/widgets/browser_widgets.rs +++ b/src/widgets/browser_widgets.rs @@ -4,7 +4,7 @@ use url::Url; use super::{nav_bar, tab_bar, BrowserView}; use crate::{ engines::{BrowserEngine, PixelFormat}, - to_url, ImageInfo, + to_url, ImageInfo, TabInfo, }; #[cfg(feature = "ultralight")] @@ -27,7 +27,7 @@ pub enum Message { DoWork, } -pub struct BrowserWidget { +pub struct BrowserWidget> { engine: Option, home: Url, url: String, @@ -38,7 +38,7 @@ pub struct BrowserWidget { view_bounds: Size, } -impl Default for BrowserWidget { +impl> Default for BrowserWidget { fn default() -> Self { let home = Url::parse(Self::HOME).unwrap(); Self { @@ -64,7 +64,7 @@ impl BrowserWidget { } } -impl BrowserWidget { +impl> BrowserWidget { const HOME: &'static str = "https://duckduckgo.com"; pub fn new() -> Self { @@ -185,7 +185,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..f7e8f43 100644 --- a/src/widgets/tab_bar.rs +++ b/src/widgets/tab_bar.rs @@ -7,12 +7,12 @@ use super::browser_widgets::Message; use crate::engines::Tab; // helper function to create navigation bar -pub fn tab_bar(tabs: Vec, active_tab: usize) -> Element<'static, Message> { +pub fn tab_bar(tabs: Vec>, active_tab: usize) -> Element<'static, Message> { let tab_bar = 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())) + tab_bar.push(idx, TabLabel::Text(tab.title())) }) .set_active_tab(&active_tab) .on_close(Message::CloseTab) From 9d6403502d3584cd1e68f9557c80ae01bae30b56 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Sat, 17 Aug 2024 20:34:48 -0600 Subject: [PATCH 2/2] fix trait errors with assosiated type --- src/engines/mod.rs | 22 ++++++++++++++++++---- src/engines/ultralight.rs | 13 ++++++++++--- src/widgets/browser_widgets.rs | 27 +++++++++++++++++---------- src/widgets/tab_bar.rs | 9 +++++---- 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/engines/mod.rs b/src/engines/mod.rs index 7da5554..e4504b1 100644 --- a/src/engines/mod.rs +++ b/src/engines/mod.rs @@ -16,7 +16,9 @@ pub enum PixelFormat { } #[allow(unused)] -pub trait BrowserEngine { +pub trait BrowserEngine { + type TabInfo: TabInfo; + fn new() -> Self; fn do_work(&self); @@ -32,10 +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 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 get_tabs(&self) -> &Tabs; + fn get_tabs_mut(&mut self) -> &mut Tabs; fn refresh(&self); fn go_forward(&self); @@ -98,6 +100,18 @@ impl Tabs { } } + 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); diff --git a/src/engines/ultralight.rs b/src/engines/ultralight.rs index 70512e6..6a20536 100644 --- a/src/engines/ultralight.rs +++ b/src/engines/ultralight.rs @@ -86,7 +86,9 @@ impl Ultralight { } } -impl BrowserEngine for Ultralight { +impl BrowserEngine for Ultralight { + type TabInfo = UltalightTabInfo; + fn new() -> Self { Ultralight::new() } @@ -165,7 +167,7 @@ impl BrowserEngine for Ultralight { &mut self.tabs } - fn new_tab(&mut self, url: &Url) { + fn new_tab(&mut self, url: &Url) -> u32 { if self .tabs .tabs @@ -226,8 +228,13 @@ impl BrowserEngine for Ultralight { cursor, }; - self.tabs.insert(Tab::new(site_url, title, tab_info)); + let tab = Tab::new(site_url, title, tab_info); + let id = tab.id; + + self.tabs.insert(tab); + return id; } + unreachable!(); } fn goto_tab(&mut self, id: u32) { diff --git a/src/widgets/browser_widgets.rs b/src/widgets/browser_widgets.rs index 0463e9f..5d65c0c 100644 --- a/src/widgets/browser_widgets.rs +++ b/src/widgets/browser_widgets.rs @@ -4,7 +4,7 @@ use url::Url; use super::{nav_bar, tab_bar, BrowserView}; use crate::{ engines::{BrowserEngine, PixelFormat}, - to_url, ImageInfo, TabInfo, + to_url, ImageInfo, }; #[cfg(feature = "ultralight")] @@ -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), @@ -27,7 +27,7 @@ pub enum Message { DoWork, } -pub struct BrowserWidget> { +pub struct BrowserWidget { engine: Option, home: Url, url: String, @@ -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(); diff --git a/src/widgets/tab_bar.rs b/src/widgets/tab_bar.rs index f7e8f43..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())) + let id = tab_bar.size(); + tab_bar.push(id as u32, TabLabel::Text(tab.title())) }) .set_active_tab(&active_tab) .on_close(Message::CloseTab)