diff --git a/ports/libmlservo/src/lib.rs b/ports/libmlservo/src/lib.rs index 08b84ada37ff..6b5bb2e6d002 100644 --- a/ports/libmlservo/src/lib.rs +++ b/ports/libmlservo/src/lib.rs @@ -40,6 +40,7 @@ use std::ffi::CStr; use std::ffi::CString; use std::io::Write; use std::os::raw::c_char; +use std::os::raw::c_void; use std::path::PathBuf; use std::rc::Rc; @@ -56,13 +57,22 @@ pub enum MLLogLevel { #[repr(transparent)] pub struct MLLogger(extern "C" fn (MLLogLevel, *const c_char)); +#[repr(transparent)] +pub struct MLHistoryUpdate(extern "C" fn (MLApp, bool, *const c_char, bool)); + +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct MLApp(*mut c_void); + const LOG_LEVEL: log::LevelFilter = log::LevelFilter::Info; #[no_mangle] pub unsafe extern "C" fn init_servo(ctxt: EGLContext, surf: EGLSurface, disp: EGLDisplay, + app: MLApp, logger: MLLogger, + history_update: MLHistoryUpdate, url: *const c_char, width: u32, height: u32, @@ -100,7 +110,9 @@ pub unsafe extern "C" fn init_servo(ctxt: EGLContext, ]); let result = Box::new(ServoInstance { + app: app, browser_id: browser_id, + history_update: history_update, servo: servo, }); Box::into_raw(result) @@ -130,10 +142,19 @@ pub unsafe extern "C" fn heartbeat_servo(servo: *mut ServoInstance) { EmbedderMsg::AllowOpeningBrowser(sender) => { let _ = sender.send(false); }, + // Update the history UI + EmbedderMsg::HistoryChanged(urls, index) => { + if let Some(url) = urls.get(index) { + if let Ok(cstr) = CString::new(url.as_str()) { + let can_go_back = index > 0; + let can_go_fwd = (index + 1) < urls.len(); + (servo.history_update.0)(servo.app, can_go_back, cstr.as_ptr(), can_go_fwd); + } + } + }, // Ignore most messages for now EmbedderMsg::ChangePageTitle(..) | EmbedderMsg::BrowserCreated(..) | - EmbedderMsg::HistoryChanged(..) | EmbedderMsg::LoadStart | EmbedderMsg::LoadComplete | EmbedderMsg::CloseBrowser | @@ -184,6 +205,22 @@ pub unsafe extern "C" fn traverse_servo(servo: *mut ServoInstance, delta: i32) { } } +#[no_mangle] +pub unsafe extern "C" fn navigate_servo(servo: *mut ServoInstance, text: *const c_char) { + if let Some(servo) = servo.as_mut() { + let text = CStr::from_ptr(text).to_str().expect("Failed to convert text to UTF-8"); + let url = ServoUrl::parse(text).unwrap_or_else(|_| { + let mut search = ServoUrl::parse("http://google.com/search") + .expect("Failed to parse search URL") + .into_url(); + search.query_pairs_mut().append_pair("q", text); + ServoUrl::from_url(search) + }); + let window_event = WindowEvent::LoadUrl(servo.browser_id, url); + servo.servo.handle_events(vec![window_event]); + } +} + #[no_mangle] pub unsafe extern "C" fn discard_servo(servo: *mut ServoInstance) { // Servo drop goes here! @@ -193,7 +230,9 @@ pub unsafe extern "C" fn discard_servo(servo: *mut ServoInstance) { } pub struct ServoInstance { + app: MLApp, browser_id: BrowserId, + history_update: MLHistoryUpdate, servo: Servo, } diff --git a/support/magicleap/Servo2D/code/inc/Servo2D.h b/support/magicleap/Servo2D/code/inc/Servo2D.h index 490c6d60ece7..29477cf3cd82 100644 --- a/support/magicleap/Servo2D/code/inc/Servo2D.h +++ b/support/magicleap/Servo2D/code/inc/Servo2D.h @@ -9,6 +9,9 @@ #include #include #include +#include +#include +#include #include typedef struct Opaque ServoInstance; @@ -48,6 +51,11 @@ class Servo2D : public lumin::LandscapeApp { */ Servo2D& operator=(Servo2D&&) = delete; + /** + * Update the browser history UI + */ + void updateHistory(bool canGoBack, const char* url, bool canGoForward); + protected: /** * Initializes the Landscape Application. @@ -93,6 +101,7 @@ class Servo2D : public lumin::LandscapeApp { virtual bool eventListener(lumin::ServerEvent* event) override; bool touchpadEventListener(lumin::ControlTouchPadInputEventData* event); bool keyEventListener(lumin::KeyInputEventData* event); + void urlBarEventListener(); /** * Get the current cursor position, with respect to the viewport. @@ -104,5 +113,8 @@ class Servo2D : public lumin::LandscapeApp { lumin::Prism* prism_ = nullptr; // represents the bounded space where the App renders. lumin::PlanarResource* plane_ = nullptr; // the plane we're rendering into lumin::QuadNode* content_node_ = nullptr; // the node containing the plane + lumin::ui::UiButton* back_button_ = nullptr; // the back button + lumin::ui::UiButton* fwd_button_ = nullptr; // the forward button + lumin::ui::UiTextEdit* url_bar_ = nullptr; // the URL bar ServoInstance* servo_ = nullptr; // the servo instance we're embedding }; diff --git a/support/magicleap/Servo2D/code/src/Servo2D.cpp b/support/magicleap/Servo2D/code/src/Servo2D.cpp index 54600362d09e..d24316490b6b 100644 --- a/support/magicleap/Servo2D/code/src/Servo2D.cpp +++ b/support/magicleap/Servo2D/code/src/Servo2D.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -35,13 +34,20 @@ void logger(MLLogLevel lvl, char* msg) { } } +// A function which updates the history ui, suitable for passing into Servo +typedef void (*MLHistoryUpdate)(Servo2D* app, bool canGoBack, char* url, bool canGoForward); +void history(Servo2D* app, bool canGoBack, char* url, bool canGoForward) { + app->updateHistory(canGoBack, url, canGoForward); +} + // The functions Servo provides for hooking up to the ML. -// For the moment, this doesn't handle input events. -extern "C" ServoInstance* init_servo(EGLContext, EGLSurface, EGLDisplay, MLLogger, - const char* url, int width, int height, float hidpi); +extern "C" ServoInstance* init_servo(EGLContext, EGLSurface, EGLDisplay, + Servo2D*, MLLogger, MLHistoryUpdate, + const char* url, int width, int height, float hidpi); extern "C" void heartbeat_servo(ServoInstance*); extern "C" void cursor_servo(ServoInstance*, float x, float y, bool triggered); extern "C" void traverse_servo(ServoInstance*, int delta); +extern "C" void navigate_servo(ServoInstance*, const char* text); extern "C" void discard_servo(ServoInstance*); // Create a Servo2D instance @@ -120,7 +126,7 @@ int Servo2D::init() { EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); // Hook into servo - servo_ = init_servo(ctx, surf, dpy, logger, "https://servo.org/", VIEWPORT_H, VIEWPORT_W, HIDPI); + servo_ = init_servo(ctx, surf, dpy, this, logger, history, "https://servo.org/", VIEWPORT_H, VIEWPORT_W, HIDPI); if (!servo_) { ML_LOG(Error, "Servo2D Failed to init servo instance"); abort(); @@ -129,24 +135,33 @@ int Servo2D::init() { // Add a callback to the back button std::string back_button_id = Servo2D_exportedNodes::backButton; - lumin::ui::UiButton* back_button = lumin::ui::UiButton::CastFrom(prism_->findNode(back_button_id, root_node)); - if (!back_button) { + back_button_ = lumin::ui::UiButton::CastFrom(prism_->findNode(back_button_id, root_node)); + if (!back_button_) { ML_LOG(Error, "Servo2D Failed to get back button"); abort(); return 1; } - back_button->onActivateSub(std::bind(traverse_servo, servo_, -1)); + back_button_->onActivateSub(std::bind(traverse_servo, servo_, -1)); // Add a callback to the forward button std::string fwd_button_id = Servo2D_exportedNodes::fwdButton; - lumin::ui::UiButton* fwd_button = lumin::ui::UiButton::CastFrom(prism_->findNode(fwd_button_id, root_node)); - if (!fwd_button) { + fwd_button_ = lumin::ui::UiButton::CastFrom(prism_->findNode(fwd_button_id, root_node)); + if (!fwd_button_) { ML_LOG(Error, "Servo2D Failed to get forward button"); abort(); return 1; } - fwd_button->onActivateSub(std::bind(traverse_servo, servo_, +1)); + fwd_button_->onActivateSub(std::bind(traverse_servo, servo_, +1)); + // Add a callback to the URL bar + std::string url_bar_id = Servo2D_exportedNodes::urlBar; + url_bar_ = lumin::ui::UiTextEdit::CastFrom(prism_->findNode(url_bar_id, root_node)); + if (!url_bar_) { + ML_LOG(Error, "Servo2D Failed to get URL bar"); + abort(); + return 1; + } + url_bar_->onFocusLostSub(std::bind(&Servo2D::urlBarEventListener, this)); return 0; } @@ -268,3 +283,13 @@ bool Servo2D::keyEventListener(lumin::KeyInputEventData* event) { cursor_servo(servo_, pos.x, pos.y, true); return true; } + +void Servo2D::urlBarEventListener() { + navigate_servo(servo_, url_bar_->getText().c_str()); +} + +void Servo2D::updateHistory(bool canGoBack, const char* url, bool canGoForward) { + back_button_->setEnabled(canGoBack); + fwd_button_->setEnabled(canGoForward); + url_bar_->setText(url); +}