aboutsummaryrefslogtreecommitdiffstats
path: root/ports
diff options
context:
space:
mode:
authorDelan Azabani <dazabani@igalia.com>2023-08-15 08:08:50 +0000
committerGitHub <noreply@github.com>2023-08-15 08:08:50 +0000
commit2778beeb7a623e0150e5084da14afa884b76f656 (patch)
tree4a227f7e718bf54dd38f9cb56d28559fa87feba6 /ports
parenta7bd9f0d43f4de13c958eaea53709e8fc0d162c8 (diff)
downloadservo-2778beeb7a623e0150e5084da14afa884b76f656.tar.gz
servo-2778beeb7a623e0150e5084da14afa884b76f656.zip
winit: initial minibrowser (#29976)
* winit: add minibrowser feature that depends on egui{,-winit} * winit: carve out some space at the top of headed windows * winit: minimal toolbar and egui/winit integration (but no painting) * winit: try to paint with egui_glow (doesn’t work yet) * winit: add comment about toolbar size * Add framebuffer object, set it as glow's target * compositing: clear only the viewport, not the whole framebuffer * plumb the actual size of the egui toolbar to webrender * fix formatting * winit: fix crash when fbo is zero * winit: don’t bother binding the framebuffer object * winit: remove unsafe and get toolbar_height * winit: location field should reflect the current top-level url * [NFC] winit: move Minibrowser out of App::run * winit: clean up toolbar height code * winit: make App own the Minibrowser if any * winit: make the go button work * winit:make the location field reflect the current top-level url * winit: allow enabling minibrowser from command line * winit: tell compositor to repaint WR and flush when we repaint * winit: fix bug where location field edits would get overridden * winit: borrow the minibrowser once in App::handle_events * winit: address todo about viewport origin coordinates * winit: fix some minor problems with comments and errors * winit: update location field once per HistoryChanged event * winit: rename Window::set_toolbar_size to set_toolbar_height * winit: take toolbar height into account in hit testing * winit: pass egui only relevant CursorMoved events * winit: scratch that, coalesce minibrowser updates instead * ensure both minibrowser and WR are repainted on every frame * compositing: only skip framebuffer clear in external present mode * winit: destroy egui glow Painter when shutting down * winit: clean up and fix license lint * fix duplicate versions lint by downgrading bytemuck_derive was egui_glow ^0.22.0 (0.22.0) → egui/bytemuck ^0.22.0 (0.22.0) → epaint/bytemuck ^0.22.0 (0.22.0) → bytemuck ^1.7.2 (1.13.1) → bytemuck_derive ^1.4 (1.4.1) → syn ^2.0.1 (2.0.28) now lock has bytemuck_derive 1.4.0 → syn ^1.0.99 (1.0.103) * fix duplicate versions lint by disabling egui-winit/links (we don’t need support for hyperlinks in our use of egui) * squelch duplicate versions lint by excluding clipboard-win * winit: fix compile warnings * winit: make gleam an optional dependency under /minibrowser * winit: remove cargo feature, since it’s not really optional * winit: extract Minibrowser and related code to separate module * winit: remove unnecessary trailing comma * winit: simplify the ServoUrl serialisation optimisation --------- Co-authored-by: atbrakhi <atbrakhi@igalia.com>
Diffstat (limited to 'ports')
-rw-r--r--ports/winit/Cargo.toml5
-rw-r--r--ports/winit/app.rs202
-rw-r--r--ports/winit/browser.rs21
-rw-r--r--ports/winit/egui_glue.rs129
-rw-r--r--ports/winit/headed_window.rs25
-rw-r--r--ports/winit/headless_window.rs8
-rw-r--r--ports/winit/main2.rs2
-rw-r--r--ports/winit/minibrowser.rs110
-rw-r--r--ports/winit/window_trait.rs2
9 files changed, 450 insertions, 54 deletions
diff --git a/ports/winit/Cargo.toml b/ports/winit/Cargo.toml
index 4f85ffb61f4..06978d3ae69 100644
--- a/ports/winit/Cargo.toml
+++ b/ports/winit/Cargo.toml
@@ -48,8 +48,13 @@ xr-profile = ["libservo/xr-profile"]
[target.'cfg(not(target_os = "android"))'.dependencies]
backtrace = { workspace = true }
clipboard = "0.5"
+egui = "0.22.0"
+egui_glow = { version = "0.22.0", features = ["winit"] }
+egui-winit = { version = "0.22.0", default-features = false, features = ["clipboard", "wayland"] }
euclid = { workspace = true }
getopts = { workspace = true }
+gleam = "0.12"
+glow = "0.12.2"
keyboard-types = { workspace = true }
lazy_static = { workspace = true }
libc = { workspace = true }
diff --git a/ports/winit/app.rs b/ports/winit/app.rs
index fe6e8fa36ef..290ca0bc38a 100644
--- a/ports/winit/app.rs
+++ b/ports/winit/app.rs
@@ -7,8 +7,10 @@
use crate::browser::Browser;
use crate::embedder::EmbedderCallbacks;
use crate::events_loop::{EventsLoop, WakerEvent};
+use crate::minibrowser::Minibrowser;
use crate::window_trait::WindowPortsMethods;
use crate::{headed_window, headless_window};
+use gleam::gl;
use winit::window::WindowId;
use winit::event_loop::EventLoopWindowTarget;
use servo::compositing::windowing::EmbedderEvent;
@@ -16,11 +18,12 @@ use servo::config::opts::{self, parse_url_or_filename};
use servo::servo_config::pref;
use servo::servo_url::ServoUrl;
use servo::Servo;
-use std::cell::{Cell, RefCell};
+use std::cell::{Cell, RefCell, RefMut};
use std::collections::HashMap;
use std::env;
use std::rc::Rc;
+use surfman::GLApi;
use webxr::glwindow::GlWindowDiscovery;
pub struct App {
@@ -29,6 +32,13 @@ pub struct App {
event_queue: RefCell<Vec<EmbedderEvent>>,
suspended: Cell<bool>,
windows: HashMap<WindowId, Rc<dyn WindowPortsMethods>>,
+ minibrowser: Option<RefCell<Minibrowser>>,
+}
+
+/// Action to be taken by the caller of [`App::handle_events`].
+enum PumpResult {
+ Shutdown,
+ Present,
}
impl App {
@@ -60,51 +70,96 @@ impl App {
servo: None,
suspended: Cell::new(false),
windows: HashMap::new(),
+ minibrowser: None,
};
+ if opts::get().minibrowser && window.winit_window().is_some() {
+ // Make sure the gl context is made current.
+ let webrender_surfman = window.webrender_surfman();
+ let webrender_gl = match webrender_surfman.connection().gl_api() {
+ GLApi::GL => unsafe { gl::GlFns::load_with(|s| webrender_surfman.get_proc_address(s)) },
+ GLApi::GLES => unsafe {
+ gl::GlesFns::load_with(|s| webrender_surfman.get_proc_address(s))
+ },
+ };
+ webrender_surfman.make_gl_context_current().unwrap();
+ debug_assert_eq!(webrender_gl.get_error(), gleam::gl::NO_ERROR);
+
+ // Set up egui context for minibrowser ui
+ // Adapted from https://github.com/emilk/egui/blob/9478e50d012c5138551c38cbee16b07bc1fcf283/crates/egui_glow/examples/pure_glow.rs
+ app.minibrowser = Some(Minibrowser::new(&webrender_surfman, &events_loop).into());
+ }
+
+ if let Some(mut minibrowser) = app.minibrowser() {
+ minibrowser.update(window.winit_window().unwrap());
+ window.set_toolbar_height(minibrowser.toolbar_height.get());
+ }
+
let ev_waker = events_loop.create_event_loop_waker();
events_loop.run_forever(move |e, w, control_flow| {
- if let winit::event::Event::NewEvents(winit::event::StartCause::Init) = e {
- let surfman = window.webrender_surfman();
+ match e {
+ winit::event::Event::NewEvents(winit::event::StartCause::Init) => {
+ let surfman = window.webrender_surfman();
+
+ let xr_discovery = if pref!(dom.webxr.glwindow.enabled) && ! opts::get().headless {
+ let window = window.clone();
+ // This should be safe because run_forever does, in fact,
+ // run forever. The event loop window target doesn't get
+ // moved, and does outlast this closure, and we won't
+ // ever try to make use of it once shutdown begins and
+ // it stops being valid.
+ let w = unsafe {
+ std::mem::transmute::<
+ &EventLoopWindowTarget<WakerEvent>,
+ &'static EventLoopWindowTarget<WakerEvent>
+ >(w.unwrap())
+ };
+ let factory = Box::new(move || Ok(window.new_glwindow(w)));
+ Some(GlWindowDiscovery::new(
+ surfman.connection(),
+ surfman.adapter(),
+ surfman.context_attributes(),
+ factory,
+ ))
+ } else {
+ None
+ };
- let xr_discovery = if pref!(dom.webxr.glwindow.enabled) && ! opts::get().headless {
let window = window.clone();
- // This should be safe because run_forever does, in fact,
- // run forever. The event loop window target doesn't get
- // moved, and does outlast this closure, and we won't
- // ever try to make use of it once shutdown begins and
- // it stops being valid.
- let w = unsafe {
- std::mem::transmute::<
- &EventLoopWindowTarget<WakerEvent>,
- &'static EventLoopWindowTarget<WakerEvent>
- >(w.unwrap())
- };
- let factory = Box::new(move || Ok(window.new_glwindow(w)));
- Some(GlWindowDiscovery::new(
- surfman.connection(),
- surfman.adapter(),
- surfman.context_attributes(),
- factory,
- ))
- } else {
- None
- };
-
- let window = window.clone();
- // Implements embedder methods, used by libservo and constellation.
- let embedder = Box::new(EmbedderCallbacks::new(
- ev_waker.clone(),
- xr_discovery,
- ));
-
- let servo_data = Servo::new(embedder, window.clone(), user_agent.clone());
- let mut servo = servo_data.servo;
- servo.handle_events(vec![EmbedderEvent::NewBrowser(get_default_url(), servo_data.browser_id)]);
- servo.setup_logging();
-
- app.windows.insert(window.id(), window.clone());
- app.servo = Some(servo);
+ // Implements embedder methods, used by libservo and constellation.
+ let embedder = Box::new(EmbedderCallbacks::new(
+ ev_waker.clone(),
+ xr_discovery,
+ ));
+
+ let servo_data = Servo::new(embedder, window.clone(), user_agent.clone());
+ let mut servo = servo_data.servo;
+
+ // If we have a minibrowser, ask the compositor to notify us when a new frame
+ // is ready to present, so that we can paint the minibrowser then present.
+ servo.set_external_present(app.minibrowser.is_some());
+
+ servo.handle_events(vec![EmbedderEvent::NewBrowser(get_default_url(), servo_data.browser_id)]);
+ servo.setup_logging();
+
+ app.windows.insert(window.id(), window.clone());
+ app.servo = Some(servo);
+ }
+
+ // TODO does windows still need this workaround?
+ // https://github.com/emilk/egui/blob/9478e50d012c5138551c38cbee16b07bc1fcf283/crates/egui_glow/examples/pure_glow.rs#L203
+ // winit::event::Event::RedrawEventsCleared => todo!(),
+ winit::event::Event::RedrawRequested(_) => {
+ if let Some(mut minibrowser) = app.minibrowser() {
+ minibrowser.update(window.winit_window().unwrap());
+
+ // Tell Servo to repaint, which will in turn allow us to repaint the
+ // minibrowser and present a complete frame without partial updates.
+ app.event_queue.borrow_mut().push(EmbedderEvent::Refresh);
+ }
+ }
+
+ _ => {}
}
// If self.servo is None here, it means that we're in the process of shutting down,
@@ -114,7 +169,23 @@ impl App {
}
// Handle the event
- app.queue_embedder_events_for_winit_event(e);
+ let mut consumed = false;
+ if let Some(mut minibrowser) = app.minibrowser() {
+ if let winit::event::Event::WindowEvent { ref event, .. } = e {
+ let response = minibrowser.context.on_event(&event);
+ if response.repaint {
+ // Request a redraw event that will in turn trigger a minibrowser update.
+ // This allows us to coalesce minibrowser updates across multiple events.
+ window.winit_window().unwrap().request_redraw();
+ }
+
+ // TODO how do we handle the tab key? (see doc for consumed)
+ consumed = response.consumed;
+ }
+ }
+ if !consumed {
+ app.queue_embedder_events_for_winit_event(e);
+ }
let animating = app.is_animating();
@@ -125,10 +196,33 @@ impl App {
*control_flow = winit::event_loop::ControlFlow::Poll;
}
- let stop = app.handle_events();
- if stop {
- *control_flow = winit::event_loop::ControlFlow::Exit;
- app.servo.take().unwrap().deinit();
+ // Consume and handle any events from the Minibrowser.
+ if let Some(mut minibrowser) = app.minibrowser() {
+ let browser = &mut app.browser.borrow_mut();
+ let app_event_queue = &mut app.event_queue.borrow_mut();
+ minibrowser.queue_embedder_events_for_minibrowser_events(browser, app_event_queue);
+ if minibrowser.update_location_in_toolbar(browser) {
+ // Update the minibrowser immediately. While we could update by requesting a
+ // redraw, doing so would delay the location update by two frames.
+ minibrowser.update(window.winit_window().unwrap());
+ }
+ }
+
+ match app.handle_events() {
+ Some(PumpResult::Shutdown) => {
+ *control_flow = winit::event_loop::ControlFlow::Exit;
+ app.servo.take().unwrap().deinit();
+ if let Some(mut minibrowser) = app.minibrowser() {
+ minibrowser.context.destroy();
+ }
+ },
+ Some(PumpResult::Present) => {
+ if let Some(mut minibrowser) = app.minibrowser() {
+ minibrowser.paint(window.winit_window().unwrap());
+ }
+ app.servo.as_mut().unwrap().present();
+ },
+ None => {},
}
});
}
@@ -190,7 +284,7 @@ impl App {
/// Window queues to the Browser queue, and from the Browser queue to Servo. We receive and
/// collect embedder messages from the various Servo components, then take them out of the
/// Servo interface so that the Browser can handle them.
- fn handle_events(&mut self) -> bool {
+ fn handle_events(&mut self) -> Option<PumpResult> {
let mut browser = self.browser.borrow_mut();
// FIXME:
@@ -212,16 +306,17 @@ impl App {
// Take any new embedder messages from Servo itself.
let mut embedder_messages = self.servo.as_mut().unwrap().get_events();
let mut need_resize = false;
+ let mut need_present = false;
loop {
// Consume and handle those embedder messages.
- browser.handle_servo_events(embedder_messages);
+ need_present |= browser.handle_servo_events(embedder_messages);
// Route embedder events from the Browser to the relevant Servo components,
// receives and collects embedder messages from various Servo components,
// and runs the compositor.
need_resize |= self.servo.as_mut().unwrap().handle_events(browser.get_events());
if browser.shutdown_requested() {
- return true;
+ return Some(PumpResult::Shutdown);
}
// Take any new embedder messages from Servo itself.
@@ -233,8 +328,17 @@ impl App {
if need_resize {
self.servo.as_mut().unwrap().repaint_synchronously();
+ need_present = true;
+ }
+ if need_present {
+ Some(PumpResult::Present)
+ } else {
+ None
}
- false
+ }
+
+ fn minibrowser(&self) -> Option<RefMut<Minibrowser>> {
+ self.minibrowser.as_ref().map(|x| x.borrow_mut())
}
}
diff --git a/ports/winit/browser.rs b/ports/winit/browser.rs
index d29b47a3ab0..e46c51e5e9f 100644
--- a/ports/winit/browser.rs
+++ b/ports/winit/browser.rs
@@ -31,6 +31,8 @@ use tinyfiledialogs::{self, MessageBoxIcon, OkCancel, YesNo};
pub struct Browser<Window: WindowPortsMethods + ?Sized> {
current_url: Option<ServoUrl>,
+ current_url_string: Option<String>,
+
/// id of the top level browsing context. It is unique as tabs
/// are not supported yet. None until created.
browser_id: Option<BrowserId>,
@@ -57,6 +59,7 @@ where
Browser {
title: None,
current_url: None,
+ current_url_string: None,
browser_id: None,
browsers: Vec::new(),
window,
@@ -72,6 +75,14 @@ where
}
}
+ pub fn browser_id(&self) -> Option<BrowserId> {
+ self.browser_id
+ }
+
+ pub fn current_url_string(&self) -> Option<&str> {
+ self.current_url_string.as_deref()
+ }
+
pub fn get_events(&mut self) -> Vec<EmbedderEvent> {
std::mem::take(&mut self.event_queue)
}
@@ -264,7 +275,9 @@ where
self.event_queue.push(event);
}
- pub fn handle_servo_events(&mut self, events: Vec<(Option<BrowserId>, EmbedderMsg)>) {
+ /// Returns true iff the caller needs to manually present a new frame.
+ pub fn handle_servo_events(&mut self, events: Vec<(Option<BrowserId>, EmbedderMsg)>) -> bool {
+ let mut need_present = false;
for (browser_id, msg) in events {
match msg {
EmbedderMsg::Status(_status) => {
@@ -437,6 +450,7 @@ where
},
EmbedderMsg::HistoryChanged(urls, current) => {
self.current_url = Some(urls[current].clone());
+ self.current_url_string = Some(urls[current].clone().into_string());
},
EmbedderMsg::SetFullscreenState(state) => {
self.window.set_fullscreen(state);
@@ -511,8 +525,13 @@ where
EmbedderMsg::ShowContextMenu(sender, ..) => {
let _ = sender.send(ContextMenuResult::Ignored);
},
+ EmbedderMsg::ReadyToPresent => {
+ need_present = true;
+ },
}
}
+
+ need_present
}
}
diff --git a/ports/winit/egui_glue.rs b/ports/winit/egui_glue.rs
new file mode 100644
index 00000000000..0e7f61a6b20
--- /dev/null
+++ b/ports/winit/egui_glue.rs
@@ -0,0 +1,129 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! A modified version of EguiGlow [from egui_glow 0.22.0][0] that retains its shapes,
+//! allowing [`EguiGlow::paint`] to be called multiple times.
+//!
+//! [0]: https://github.com/emilk/egui/blob/0.22.0/crates/egui_glow/src/winit.rs
+
+// Copyright (c) 2018-2021 Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
+//
+// Permission is hereby granted, free of charge, to any
+// person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the
+// Software without restriction, including without
+// limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice
+// shall be included in all copies or substantial portions
+// of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+use egui_glow::ShaderVersion;
+pub use egui_winit;
+use egui_winit::winit;
+pub use egui_winit::EventResponse;
+
+/// Use [`egui`] from a [`glow`] app based on [`winit`].
+pub struct EguiGlow {
+ pub egui_ctx: egui::Context,
+ pub egui_winit: egui_winit::State,
+ pub painter: egui_glow::Painter,
+
+ shapes: Vec<egui::epaint::ClippedShape>,
+ textures_delta: egui::TexturesDelta,
+}
+
+impl EguiGlow {
+ /// For automatic shader version detection set `shader_version` to `None`.
+ pub fn new<E>(
+ event_loop: &winit::event_loop::EventLoopWindowTarget<E>,
+ gl: std::sync::Arc<glow::Context>,
+ shader_version: Option<ShaderVersion>,
+ ) -> Self {
+ let painter = egui_glow::Painter::new(gl, "", shader_version)
+ .map_err(|err| {
+ log::error!("error occurred in initializing painter:\n{err}");
+ })
+ .unwrap();
+
+ Self {
+ egui_ctx: Default::default(),
+ egui_winit: egui_winit::State::new(event_loop),
+ painter,
+ shapes: Default::default(),
+ textures_delta: Default::default(),
+ }
+ }
+
+ pub fn on_event(&mut self, event: &winit::event::WindowEvent<'_>) -> EventResponse {
+ self.egui_winit.on_event(&self.egui_ctx, event)
+ }
+
+ /// Returns the `Duration` of the timeout after which egui should be repainted even if there's no new events.
+ ///
+ /// Call [`Self::paint`] later to paint.
+ pub fn run(
+ &mut self,
+ window: &winit::window::Window,
+ run_ui: impl FnMut(&egui::Context),
+ ) -> std::time::Duration {
+ let raw_input = self.egui_winit.take_egui_input(window);
+ let egui::FullOutput {
+ platform_output,
+ repaint_after,
+ textures_delta,
+ shapes,
+ } = self.egui_ctx.run(raw_input, run_ui);
+
+ self.egui_winit
+ .handle_platform_output(window, &self.egui_ctx, platform_output);
+
+ self.shapes = shapes;
+ self.textures_delta.append(textures_delta);
+ repaint_after
+ }
+
+ /// Paint the results of the last call to [`Self::run`].
+ pub fn paint(&mut self, window: &winit::window::Window) {
+ /////// let shapes = std::mem::take(&mut self.shapes);
+ let shapes = &self.shapes;
+ let mut textures_delta = std::mem::take(&mut self.textures_delta);
+
+ for (id, image_delta) in textures_delta.set {
+ self.painter.set_texture(id, &image_delta);
+ }
+
+ /////// let clipped_primitives = self.egui_ctx.tessellate(shapes);
+ let clipped_primitives = self.egui_ctx.tessellate(shapes.clone());
+ let dimensions: [u32; 2] = window.inner_size().into();
+ self.painter.paint_primitives(
+ dimensions,
+ self.egui_ctx.pixels_per_point(),
+ &clipped_primitives,
+ );
+
+ for id in textures_delta.free.drain(..) {
+ self.painter.free_texture(id);
+ }
+ }
+
+ /// Call to release the allocated graphics resources.
+ pub fn destroy(&mut self) {
+ self.painter.destroy();
+ }
+}
diff --git a/ports/winit/headed_window.rs b/ports/winit/headed_window.rs
index c39218bc5d7..e57e0400389 100644
--- a/ports/winit/headed_window.rs
+++ b/ports/winit/headed_window.rs
@@ -51,6 +51,7 @@ pub struct Window {
webrender_surfman: WebrenderSurfman,
screen_size: Size2D<u32, DeviceIndependentPixel>,
inner_size: Cell<Size2D<u32, DeviceIndependentPixel>>,
+ toolbar_height: Cell<f32>,
mouse_down_button: Cell<Option<winit::event::MouseButton>>,
mouse_down_point: Cell<Point2D<i32, DevicePixel>>,
primary_monitor: winit::monitor::MonitorHandle,
@@ -156,6 +157,7 @@ impl Window {
device_pixels_per_px,
xr_window_poses: RefCell::new(vec![]),
modifiers_state: Cell::new(ModifiersState::empty()),
+ toolbar_height: Cell::new(0.0),
}
}
@@ -405,8 +407,9 @@ impl WindowPortsMethods for Window {
}
},
winit::event::WindowEvent::CursorMoved { position, .. } => {
- let (x, y): (i32, i32) = position.into();
- self.mouse_pos.set(Point2D::new(x, y));
+ let (x, y): (f64, f64) = position.into();
+ let y = y - f64::from(self.toolbar_height.get());
+ self.mouse_pos.set(Point2D::new(x, y).to_i32());
self.event_queue
.borrow_mut()
.push(EmbedderEvent::MouseWindowMoveEventClass(Point2D::new(
@@ -504,6 +507,14 @@ impl WindowPortsMethods for Window {
self.xr_window_poses.borrow_mut().push(pose.clone());
Box::new(XRWindow { winit_window, pose })
}
+
+ fn winit_window(&self) -> Option<&winit::window::Window> {
+ Some(&self.winit_window)
+ }
+
+ fn set_toolbar_height(&self, height: f32) {
+ self.toolbar_height.set(height);
+ }
}
impl WindowMethods for Window {
@@ -525,8 +536,14 @@ impl WindowMethods for Window {
let PhysicalSize { width, height } = self
.winit_window
.inner_size();
- let inner_size = (Size2D::new(width as f32, height as f32) * dpr).to_i32();
- let viewport = DeviceIntRect::new(Point2D::zero(), inner_size);
+
+ // Subtract the minibrowser toolbar height if any
+ let toolbar_height = self.toolbar_height.get();
+ let inner_size = Size2D::new(width as f32, height as f32) * dpr;
+ let viewport_size = inner_size - Size2D::new(0f32, toolbar_height);
+ let viewport_origin = DeviceIntPoint::zero(); // bottom left
+ let viewport = DeviceIntRect::new(viewport_origin, viewport_size.to_i32());
+
let framebuffer = DeviceIntSize::from_untyped(viewport.size.to_untyped());
EmbedderCoordinates {
viewport,
diff --git a/ports/winit/headless_window.rs b/ports/winit/headless_window.rs
index 0347ae47216..ad8d29011fd 100644
--- a/ports/winit/headless_window.rs
+++ b/ports/winit/headless_window.rs
@@ -108,6 +108,14 @@ impl WindowPortsMethods for Window {
) -> Box<dyn webxr::glwindow::GlWindow> {
unimplemented!()
}
+
+ fn winit_window(&self) -> Option<&winit::window::Window> {
+ None
+ }
+
+ fn set_toolbar_height(&self, _height: f32) {
+ unimplemented!("headless Window only")
+ }
}
impl WindowMethods for Window {
diff --git a/ports/winit/main2.rs b/ports/winit/main2.rs
index 51997ab2c5b..07d931c21ab 100644
--- a/ports/winit/main2.rs
+++ b/ports/winit/main2.rs
@@ -14,11 +14,13 @@ mod app;
mod backtrace;
mod browser;
mod crash_handler;
+mod egui_glue;
mod embedder;
mod events_loop;
mod headed_window;
mod headless_window;
mod keyutils;
+mod minibrowser;
mod prefs;
mod resources;
mod window_trait;
diff --git a/ports/winit/minibrowser.rs b/ports/winit/minibrowser.rs
new file mode 100644
index 00000000000..48afc9d5936
--- /dev/null
+++ b/ports/winit/minibrowser.rs
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+ use std::{cell::{RefCell, Cell}, sync::Arc};
+
+use egui::TopBottomPanel;
+use servo::{servo_url::ServoUrl, compositing::windowing::EmbedderEvent};
+use servo::webrender_surfman::WebrenderSurfman;
+
+use crate::{egui_glue::EguiGlow, events_loop::EventsLoop, browser::Browser, window_trait::WindowPortsMethods};
+
+pub struct Minibrowser {
+ pub context: EguiGlow,
+ pub event_queue: RefCell<Vec<MinibrowserEvent>>,
+ pub toolbar_height: Cell<f32>,
+ location: RefCell<String>,
+
+ /// Whether the location has been edited by the user without clicking Go.
+ location_dirty: Cell<bool>,
+}
+
+pub enum MinibrowserEvent {
+ /// Go button clicked.
+ Go,
+}
+
+impl Minibrowser {
+ pub fn new(webrender_surfman: &WebrenderSurfman, events_loop: &EventsLoop) -> Self {
+ let gl = unsafe {
+ glow::Context::from_loader_function(|s| {
+ webrender_surfman.get_proc_address(s)
+ })
+ };
+
+ Self {
+ context: EguiGlow::new(events_loop.as_winit(), Arc::new(gl), None),
+ event_queue: RefCell::new(vec![]),
+ toolbar_height: 0f32.into(),
+ location: RefCell::new(String::default()),
+ location_dirty: false.into(),
+ }
+ }
+
+ /// Update the minibrowser, but don’t paint.
+ pub fn update(&mut self, window: &winit::window::Window) {
+ let Self { context, event_queue, location, location_dirty, toolbar_height } = self;
+ let _duration = context.run(window, |ctx| {
+ TopBottomPanel::top("toolbar").show(ctx, |ui| {
+ ui.allocate_ui_with_layout(
+ ui.available_size(),
+ egui::Layout::right_to_left(egui::Align::Center),
+ |ui| {
+ if ui.button("go").clicked() {
+ event_queue.borrow_mut().push(MinibrowserEvent::Go);
+ location_dirty.set(false);
+ }
+ if ui.add_sized(
+ ui.available_size(),
+ egui::TextEdit::singleline(&mut *location.borrow_mut()),
+ ).changed() {
+ location_dirty.set(true);
+ }
+ },
+ );
+ });
+
+ toolbar_height.set(ctx.used_rect().height());
+ });
+ }
+
+ /// Paint the minibrowser, as of the last update.
+ pub fn paint(&mut self, window: &winit::window::Window) {
+ self.context.paint(window);
+ }
+
+ /// Takes any outstanding events from the [Minibrowser], converting them to [EmbedderEvent] and
+ /// routing those to the App event queue.
+ pub fn queue_embedder_events_for_minibrowser_events(
+ &self, browser: &Browser<dyn WindowPortsMethods>,
+ app_event_queue: &mut Vec<EmbedderEvent>,
+ ) {
+ for event in self.event_queue.borrow_mut().drain(..) {
+ match event {
+ MinibrowserEvent::Go => {
+ let browser_id = browser.browser_id().unwrap();
+ let location = self.location.borrow();
+ let Ok(url) = ServoUrl::parse(&location) else {
+ warn!("failed to parse location");
+ break;
+ };
+ app_event_queue.push(EmbedderEvent::LoadUrl(browser_id, url));
+ },
+ }
+ }
+ }
+
+ /// Updates the location field when the [Browser] says it has changed, unless the user has
+ /// started editing it without clicking Go.
+ pub fn update_location_in_toolbar(&mut self, browser: &mut Browser<dyn WindowPortsMethods>) -> bool {
+ if !self.location_dirty.get() {
+ if let Some(location) = browser.current_url_string() {
+ self.location = RefCell::new(location.to_owned());
+ return true;
+ }
+ }
+
+ false
+ }
+}
diff --git a/ports/winit/window_trait.rs b/ports/winit/window_trait.rs
index 26aea605454..8f780735c23 100644
--- a/ports/winit/window_trait.rs
+++ b/ports/winit/window_trait.rs
@@ -31,4 +31,6 @@ pub trait WindowPortsMethods: WindowMethods {
&self,
events_loop: &winit::event_loop::EventLoopWindowTarget<WakerEvent>
) -> Box<dyn webxr::glwindow::GlWindow>;
+ fn winit_window(&self) -> Option<&winit::window::Window>;
+ fn set_toolbar_height(&self, height: f32);
}