use gpui::{ App, AppContext as _, Bounds, Context, Entity, Focusable, Render, Stateful, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size, }; use gpui_platform::application; use gpui_servo::{ServoConfig, ServoEngine, WebView}; use url::Url; fn parse_url(input: &str) -> Option { let input = input.trim(); if input.is_empty() { return None; } Url::parse(input) .or_else(|_| Url::parse(&format!("https://{input}"))) .ok() } fn nav_button(label: &'static str, id: &'static str, enabled: bool) -> Stateful { div() .px_2() .py_1() .rounded_md() .text_sm() .text_color(if enabled { rgb(0xe4e4e7) } else { rgb(0x52525b) }) .when(enabled, |el| { el.hover(|el| el.bg(rgb(0x27272a))).cursor_pointer() }) .child(label) .id(id) } fn main() { let start = std::env::args() .nth(1) .and_then(|arg| parse_url(&arg)) .unwrap_or_else(|| { Url::parse("https://en.wikipedia.org/wiki/Meridian").expect("valid url") }); application().run(move |cx: &mut App| { gpui_servo::init(cx); let bounds = Bounds::centered(None, size(px(1100.), px(720.)), cx); cx.open_window( WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), ..Default::default() }, move |window, cx| { let engine = cx.new(|cx| { ServoEngine::new(Some(start.clone()), ServoConfig::default(), window, cx) }); cx.new(|cx| BrowserView::new(engine, window, cx)) }, ) .expect("failed to open the browser window"); cx.activate(true); }); } struct BrowserView { engine: Entity, webview: Entity, } impl BrowserView { fn new(engine: Entity, window: &mut Window, cx: &mut Context) -> Self { let webview = cx.new(|cx| WebView::new(engine.clone(), cx).keyboard(true)); // hand keyboard focus to the page window.focus(&webview.read(cx).focus_handle(cx), cx); Self { engine, webview } } } impl Render for BrowserView { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let engine = self.engine.read(cx); let url = engine.url().map(|url| url.to_string()).unwrap_or_default(); let loading = engine.is_loading(); let can_back = engine.can_go_back(); let can_forward = engine.can_go_forward(); let back_engine = self.engine.clone(); let forward_engine = self.engine.clone(); let reload_engine = self.engine.clone(); div() .size_full() .flex() .flex_col() .bg(rgb(0x18181b)) .child( div() .flex() .flex_row() .items_center() .gap_2() .px_3() .py_2() .bg(rgb(0x09090b)) .border_b_1() .border_color(rgb(0x27272a)) .child( nav_button("Back", "back", can_back).on_click(move |_, _, cx| { back_engine.update(cx, |engine, _| engine.go_back()); }), ) .child(nav_button("Forward", "forward", can_forward).on_click( move |_, _, cx| { forward_engine.update(cx, |engine, _| engine.go_forward()); }, )) .child( nav_button("Reload", "reload", true).on_click(move |_, _, cx| { reload_engine.update(cx, |engine, _| engine.reload()); }), ) .child( div() .flex_1() .min_w_0() .text_sm() .text_color(if loading { rgb(0x7a7df3) } else { rgb(0x6b7280) }) .line_clamp(1) .child(url), ), ) .child(div().flex_1().min_h_0().child(self.webview.clone())) } }