From 8a9c45af6147aed82d49186c2ea1f581ce773788 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Wed, 14 Apr 2021 19:00:34 -0700 Subject: [PATCH] Building out browser example. - Includes test case to reproduce textfield dealloc bug. --- examples/{browser.rs => browser/main.rs} | 70 +++++++++------ examples/browser/toolbar.rs | 105 +++++++++++++++++++++++ src/input/macos.rs | 2 +- src/webview/mod.rs | 14 +++ 4 files changed, 163 insertions(+), 28 deletions(-) rename examples/{browser.rs => browser/main.rs} (64%) create mode 100644 examples/browser/toolbar.rs diff --git a/examples/browser.rs b/examples/browser/main.rs similarity index 64% rename from examples/browser.rs rename to examples/browser/main.rs index 736c29c..e34219d 100644 --- a/examples/browser.rs +++ b/examples/browser/main.rs @@ -1,14 +1,29 @@ //! This example showcases setting up a basic application and window, setting up some views to //! work with autolayout, and some basic ways to handle colors. +use cacao::notification_center::Dispatcher; use cacao::webview::{WebView, WebViewConfig, WebViewDelegate}; -use cacao::input::{TextField, TextFieldDelegate}; - use cacao::macos::{App, AppDelegate}; -use cacao::macos::toolbar::{Toolbar, ToolbarItem, ItemIdentifier, ToolbarDelegate}; use cacao::macos::menu::{Menu, MenuItem}; -use cacao::macos::window::{Window, WindowConfig, WindowDelegate}; +use cacao::macos::toolbar::Toolbar; +use cacao::macos::window::{Window, WindowConfig, WindowDelegate, WindowToolbarStyle}; + +mod toolbar; +use toolbar::BrowserToolbar; + +#[derive(Debug)] +pub enum Action { + Back, + Forwards, + Load(String) +} + +impl Action { + pub fn dispatch(self) { + App::::dispatch_main(self); + } +} struct BasicApp { window: Window @@ -61,33 +76,21 @@ impl AppDelegate for BasicApp { } } -pub struct URLBar; +impl Dispatcher for BasicApp { + type Message = Action; -impl TextFieldDelegate for URLBar { - const NAME: &'static str = "URLBar"; -} + fn on_ui_message(&self, message: Self::Message) { + let window = self.window.delegate.as_ref().unwrap(); + let webview = &window.content; -struct BrowserToolbar { - url_bar: TextField -} - -impl BrowserToolbar { - pub fn new() -> Self { - BrowserToolbar { - url_bar: TextField::with(URLBar) + match message { + Action::Back => { webview.go_back(); }, + Action::Forwards => { webview.go_forward(); }, + Action::Load(url) => { window.load_url(&url); } } } } -impl ToolbarDelegate for BrowserToolbar { - const NAME: &'static str = "BrowserToolbar"; - - fn allowed_item_identifiers(&self) -> Vec { vec![] } - fn default_item_identifiers(&self) -> Vec { vec![] } - - fn item_for(&self, _identifier: &str) -> &ToolbarItem { std::unreachable!(); } -} - #[derive(Default)] pub struct WebViewInstance; @@ -105,6 +108,11 @@ impl AppWindow { content: WebView::with(WebViewConfig::default(), WebViewInstance::default()) } } + + pub fn load_url(&self, url: &str) { + self.toolbar.delegate.as_ref().unwrap().set_url(url); + self.content.load_url(url); + } } impl WindowDelegate for AppWindow { @@ -112,17 +120,25 @@ impl WindowDelegate for AppWindow { fn did_load(&mut self, window: Window) { window.set_title("Browser Example"); + window.set_autosave_name("CacaoBrowserExample"); window.set_minimum_content_size(400., 400.); window.set_toolbar(&self.toolbar); window.set_content_view(&self.content); - self.content.load_url("https://www.duckduckgo.com/"); + self.load_url("https://www.duckduckgo.com/"); } } fn main() { App::new("com.test.window", BasicApp { - window: Window::with(WindowConfig::default(), AppWindow::new()) + window: Window::with({ + let mut config = WindowConfig::default(); + + // This flag is necessary for Big Sur to use the correct toolbar style. + config.toolbar_style = WindowToolbarStyle::Expanded; + + config + }, AppWindow::new()) }).run(); } diff --git a/examples/browser/toolbar.rs b/examples/browser/toolbar.rs new file mode 100644 index 0000000..14f817b --- /dev/null +++ b/examples/browser/toolbar.rs @@ -0,0 +1,105 @@ + +use cacao::objc::{msg_send, sel, sel_impl}; + +use cacao::button::Button; +use cacao::input::{TextField, TextFieldDelegate}; + +use cacao::macos::toolbar::{Toolbar, ToolbarDisplayMode, ToolbarItem, ItemIdentifier, ToolbarDelegate}; + +use super::Action; + +const BACK_BUTTON: &'static str = "BackButton"; +const FWDS_BUTTON: &'static str = "FwdsButton"; +const URL_BAR: &'static str = "URLBar"; + +#[derive(Debug)] +pub struct URLBar; + +impl TextFieldDelegate for URLBar { + const NAME: &'static str = "URLBar"; + + fn text_did_end_editing(&self, value: &str) { + Action::Load(value.to_string()).dispatch(); + } +} + +#[derive(Debug)] +pub struct BrowserToolbar { + back_item: ToolbarItem, + forwards_item: ToolbarItem, + url_bar: TextField, + url_bar_item: ToolbarItem +} + +impl BrowserToolbar { + pub fn new() -> Self { + let back_button = Button::new("Back"); + let mut back_item = ToolbarItem::new(BACK_BUTTON); + back_item.set_button(back_button); + back_item.set_action(|| Action::Back.dispatch()); + + let forwards_button = Button::new("Forwards"); + let mut forwards_item = ToolbarItem::new(FWDS_BUTTON); + forwards_item.set_button(forwards_button); + forwards_item.set_action(|| Action::Forwards.dispatch()); + + let url_bar = TextField::with(URLBar); + let url_bar_item = ToolbarItem::new(URL_BAR); + + // We cheat for now to link these, as there's no API for Toolbar yet + // to support arbitrary view types. The framework is designed to support this kind of + // cheating, though: it's not outlandish to need to just manage things yourself when it + // comes to Objective-C/AppKit sometimes. + // + // As long as we keep hold of things here and they all drop together, it's relatively safe. + url_bar.objc.with_mut(|obj| unsafe { + let _: () = msg_send![&*url_bar_item.objc, setView:&*obj]; + }); + + BrowserToolbar { + back_item, + forwards_item, + url_bar, + url_bar_item + } + } + + pub fn set_url(&self, url: &str) { + self.url_bar.set_text(url); + } + + fn item_identifiers(&self) -> Vec { + vec![ + ItemIdentifier::Custom(BACK_BUTTON), + ItemIdentifier::Custom(FWDS_BUTTON), + ItemIdentifier::Space, + ItemIdentifier::Custom(URL_BAR), + ItemIdentifier::Space + ] + } +} + +impl ToolbarDelegate for BrowserToolbar { + const NAME: &'static str = "BrowserToolbar"; + + fn did_load(&mut self, toolbar: Toolbar) { + toolbar.set_display_mode(ToolbarDisplayMode::IconOnly); + } + + fn allowed_item_identifiers(&self) -> Vec { + self.item_identifiers() + } + + fn default_item_identifiers(&self) -> Vec { + self.item_identifiers() + } + + fn item_for(&self, identifier: &str) -> &ToolbarItem { + match identifier { + BACK_BUTTON => &self.back_item, + FWDS_BUTTON => &self.forwards_item, + URL_BAR => &self.url_bar_item, + _ => { std::unreachable!(); } + } + } +} diff --git a/src/input/macos.rs b/src/input/macos.rs index 59bfebd..9713e3a 100644 --- a/src/input/macos.rs +++ b/src/input/macos.rs @@ -13,7 +13,7 @@ use crate::utils::load; /// Called when editing this text field has ended (e.g. user pressed enter). extern "C" fn text_did_end_editing(this: &mut Object, _: Sel, _info: id) { let view = load::(this, TEXTFIELD_DELEGATE_PTR); - let s = NSString::retain(unsafe { msg_send![this, stringValue] }); + let s = NSString::from_retained(unsafe { msg_send![this, stringValue] }); view.text_did_end_editing(s.to_str()); } diff --git a/src/webview/mod.rs b/src/webview/mod.rs index f46e279..45e5e3c 100644 --- a/src/webview/mod.rs +++ b/src/webview/mod.rs @@ -242,6 +242,20 @@ impl WebView { let _: () = msg_send![&*obj, loadRequest:request]; }); } + + /// Go back in history, if possible. + pub fn go_back(&self) { + self.objc.with_mut(|obj| unsafe { + let _: () = msg_send![&*obj, goBack]; + }); + } + + /// Go forward in history, if possible. + pub fn go_forward(&self) { + self.objc.with_mut(|obj| unsafe { + let _: () = msg_send![&*obj, goForward]; + }); + } } impl Layout for WebView {