Skip to content

Commit

Permalink
feat: multi-window support (#109)
Browse files Browse the repository at this point in the history
* Init window after event loop inited

* Fix typo

* Fix resize event

* Return boolean after resize

* Update verso to hold hashmap of windows

* Improve shutdown logic

* Fix scale factor on Linux

* Present directly after composite

* Add some fields to prepare for multisurface

* Add initial multi-surface support

* Add document ID field in window

* Update to latest servo

* Create correct window spawning order

* Create separate display list for different surface

* Fix flashing

* Improve set_painting_order safety

* Improve webview creation flow

* Update unsupported log to trace

* Fix resize and add more debug logs

* Remove unecessary step

* Address review changes

* Update compositor document

---------

Co-authored-by: Wu Yuwei <Wu Yu Wei>
wusyong authored Aug 9, 2024
1 parent 4c12250 commit 07a88a8
Showing 7 changed files with 580 additions and 437 deletions.
162 changes: 81 additions & 81 deletions Cargo.lock

Large diffs are not rendered by default.

49 changes: 24 additions & 25 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -62,36 +62,35 @@ surfman = { version = "0.9", features = ["chains", "sm-raw-window-handle-05"] }
thiserror = "1.0"
winit = { version = "0.29", features = ["rwh_05"] }
# Servo repo crates
# libservo = { git = "https://github.com/servo/servo.git", rev = "5e59988", features = ["max_log_level", "native-bluetooth", "webdriver"] }
base = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
bluetooth = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
bluetooth_traits = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
canvas = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
compositing_traits = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
constellation = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
devtools = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
embedder_traits = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
fonts = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
layout_thread_2020 = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
media = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
net = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
profile = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
profile_traits = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
script = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
script_traits = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
servo_config = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
servo_geometry = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
servo_url = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
webdriver_server = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
webrender_traits = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
webgpu = { git = "https://github.com/servo/servo.git", rev = "5e59988" }
base = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
bluetooth = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
bluetooth_traits = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
canvas = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
compositing_traits = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
constellation = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
devtools = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
embedder_traits = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
fonts = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
layout_thread_2020 = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
media = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
net = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
profile = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
profile_traits = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
script = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
script_traits = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
servo_config = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
servo_geometry = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
servo_url = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
webdriver_server = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
webrender_traits = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
webgpu = { git = "https://github.com/servo/servo.git", rev = "28430ba" }
# Servo org crates
servo-media = { git = "https://github.com/servo/media" }
servo-media-dummy = { git = "https://github.com/servo/media" }
style = { git = "https://github.com/servo/stylo", branch = "2024-07-16", features = ["servo"] }
style_traits = { git = "https://github.com/servo/stylo", branch = "2024-07-16", features = ["servo"] }
webrender = { git = "https://github.com/servo/webrender", branch = "0.64", features = ["capture"] }
webrender_api = { git = "https://github.com/servo/webrender", branch = "0.64" }
webrender = { git = "https://github.com/servo/webrender", branch = "0.65", features = ["capture"] }
webrender_api = { git = "https://github.com/servo/webrender", branch = "0.65" }
webxr = { git = "https://github.com/servo/webxr", features = ["headless"] }
# Packager feature
cargo-packager-resource-resolver = { version = "0.1.1", features = [
266 changes: 151 additions & 115 deletions src/compositor.rs

Large diffs are not rendered by default.

62 changes: 17 additions & 45 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -3,64 +3,36 @@

use verso::config::Config;
use verso::{Result, Verso};
use winit::event::{Event, StartCause};
use winit::event_loop::EventLoop;
use winit::event_loop::{ControlFlow, DeviceEvents};
use winit::{event_loop::EventLoop, window::WindowBuilder};

/* window decoration */
#[cfg(macos)]
use cocoa::appkit::{NSWindow, NSWindowStyleMask, NSWindowTitleVisibility};
#[cfg(macos)]
use objc::runtime::Object;
#[cfg(macos)]
use raw_window_handle::{AppKitWindowHandle, HasRawWindowHandle, RawWindowHandle};
#[cfg(macos)]
use winit::dpi::LogicalPosition;

fn main() -> Result<()> {
let event_loop = EventLoop::new()?;
event_loop.listen_device_events(DeviceEvents::Never);
let window = WindowBuilder::new()
// .with_decorations(false)
.build(&event_loop)?;

#[cfg(macos)]
unsafe {
let rwh = window.raw_window_handle();
if let RawWindowHandle::AppKit(AppKitWindowHandle { ns_window, .. }) = rwh {
decorate_window(ns_window as *mut Object, LogicalPosition::new(8.0, 40.0));
}
}

let config = Config::new(resources_dir_path().unwrap());
let mut verso = Verso::new(window, event_loop.create_proxy(), config);
let proxy = event_loop.create_proxy();
let mut verso = None;
event_loop.run(move |event, evl| {
verso.run(event);
if verso.finished_shutting_down() {
evl.exit();
} else if verso.is_animating() {
evl.set_control_flow(ControlFlow::Poll);
if let Event::NewEvents(StartCause::Init) = event {
let config = Config::new(resources_dir_path().unwrap());
verso = Some(Verso::new(evl, proxy.clone(), config));
} else {
evl.set_control_flow(ControlFlow::Wait);
if let Some(v) = &mut verso {
v.run(event, evl);
if v.finished_shutting_down() {
evl.exit();
} else if v.is_animating() {
evl.set_control_flow(ControlFlow::Poll);
} else {
evl.set_control_flow(ControlFlow::Wait);
}
}
}
})?;

Ok(())
}

#[cfg(macos)]
pub unsafe fn decorate_window(window: *mut Object, _position: LogicalPosition<f64>) {
NSWindow::setTitlebarAppearsTransparent_(window, cocoa::base::YES);
NSWindow::setTitleVisibility_(window, NSWindowTitleVisibility::NSWindowTitleHidden);
NSWindow::setStyleMask_(
window,
NSWindowStyleMask::NSTitledWindowMask
| NSWindowStyleMask::NSFullSizeContentViewWindowMask
| NSWindowStyleMask::NSClosableWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask,
);
}

fn resources_dir_path() -> Option<std::path::PathBuf> {
#[cfg(feature = "packager")]
let root_dir = {
154 changes: 108 additions & 46 deletions src/verso.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::{
borrow::Cow,
collections::HashMap,
path::PathBuf,
sync::{atomic::Ordering, Arc},
};

use arboard::Clipboard;
use base::id::WebViewId;
use bluetooth::BluetoothThreadFactory;
use bluetooth_traits::BluetoothRequest;
use canvas::{
@@ -17,7 +19,7 @@ use compositing_traits::{
use constellation::{Constellation, FromCompositorLogger, InitialConstellationState};
use crossbeam_channel::{unbounded, Sender};
use devtools;
use embedder_traits::{EmbedderProxy, EmbedderReceiver, EventLoopWaker};
use embedder_traits::{EmbedderMsg, EmbedderProxy, EmbedderReceiver, EventLoopWaker};
use euclid::Scale;
use fonts::FontCacheThread;
use gleam::gl;
@@ -33,25 +35,27 @@ use servo_config::{opts, pref};
use servo_url::ServoUrl;
use style;
use surfman::GLApi;
use units::DeviceIntRect;
use webgpu;
use webrender::{create_webrender_instance, ShaderPrecacheFlags, WebRenderOptions};
use webrender_api::*;
use webrender_traits::*;
use winit::{
event::{Event, StartCause},
event_loop::EventLoopProxy,
window::Window as WinitWindow,
event::{Event, WindowEvent},
event_loop::{EventLoopProxy, EventLoopWindowTarget},
window::WindowId,
};

use crate::{
compositor::{IOCompositor, InitialCompositorState, ShutdownState},
config::Config,
webview::WebView,
window::Window,
};

/// Main entry point of Verso browser.
pub struct Verso {
window: Window,
windows: HashMap<WindowId, Window>,
compositor: Option<IOCompositor>,
constellation_sender: Sender<ConstellationMsg>,
embedder_receiver: EmbedderReceiver,
@@ -80,11 +84,11 @@ impl Verso {
/// - Font cache
/// - Canvas
/// - Constellation
pub fn new(window: WinitWindow, proxy: EventLoopProxy<()>, config: Config) -> Self {
pub fn new(evl: &EventLoopWindowTarget<()>, proxy: EventLoopProxy<()>, config: Config) -> Self {
// Initialize configurations and Verso window
let resource_dir = config.resource_dir.clone();
config.init();
let (window, rendering_context) = Window::new(window);
let (window, rendering_context) = Window::new(evl);
let event_loop_waker = Box::new(Waker(proxy));
let opts = opts::get();

@@ -106,6 +110,7 @@ impl Verso {
gl::GlesFns::load_with(|s| rendering_context.get_proc_address(s))
},
};

// Make sure the gl context is made current.
rendering_context.make_gl_context_current().unwrap();
debug_assert_eq!(webrender_gl.get_error(), gl::NO_ERROR,);
@@ -162,7 +167,7 @@ impl Verso {
debug_flags.set(DebugFlags::PROFILER_DBG, opts.debug.webrender_stats);

let render_notifier = Box::new(RenderNotifier::new(compositor_sender.clone()));
let clear_color = ColorF::new(1., 1., 1., 0.);
let clear_color = ColorF::new(0., 0., 0., 0.);
create_webrender_instance(
webrender_gl.clone(),
render_notifier,
@@ -191,7 +196,8 @@ impl Verso {
.expect("Unable to initialize webrender!")
};
let webrender_api = webrender_api_sender.create_api();
let webrender_document = webrender_api.add_document(window.size());
let webrender_document =
webrender_api.add_document_with_id(window.size(), u64::from(window.id()) as u32);

// Initialize js engine if it's single process mode
let js_engine_setup = if !opts.multiprocess {
@@ -336,6 +342,7 @@ impl Verso {
// The compositor coordinates with the client window to create the final
// rendered page and display it somewhere.
let compositor = IOCompositor::new(
window.id(),
window.size(),
Scale::new(window.scale_factor() as f32),
InitialCompositorState {
@@ -355,9 +362,22 @@ impl Verso {
opts.debug.convert_mouse_to_touch,
);

// Send the constellation message to start Panel UI
// TODO: Should become a window method
let panel_id = window.panel.as_ref().unwrap().webview_id;
let path = resource_dir.join("panel.html");
let url = ServoUrl::from_file_path(path.to_str().unwrap()).unwrap();
send_to_constellation(
&constellation_sender,
ConstellationMsg::NewWebView(url, panel_id),
);

let mut windows = HashMap::new();
windows.insert(window.id(), window);

// Create Verso instance
let verso = Verso {
window,
windows,
compositor: Some(compositor),
constellation_sender,
embedder_receiver,
@@ -374,67 +394,109 @@ impl Verso {
///
/// - Handle Winit's event, updating Compositor and sending messages to Constellation.
/// - Handle Servo's messages and updating Compositor again.
pub fn run(&mut self, event: Event<()>) {
pub fn run(&mut self, event: Event<()>, evl: &EventLoopWindowTarget<()>) {
self.handle_winit_event(event);
self.handle_servo_messages();
self.handle_servo_messages(evl);
if self.windows.is_empty() {
self.compositor
.as_mut()
.map(IOCompositor::maybe_start_shutting_down);
}
}

/// Handle Winit events
fn handle_winit_event(&mut self, event: Event<()>) {
log::trace!("Verso is handling Winit event: {event:?}");
match event {
Event::NewEvents(StartCause::Init) => {
// Send the constellation message to start Panel UI
let panel_id = self.window.panel.webview_id;
let path = self.resource_dir.join("panel.html");
let url = ServoUrl::from_file_path(path.to_str().unwrap()).unwrap();
send_to_constellation(
&self.constellation_sender,
ConstellationMsg::NewWebView(url, panel_id),
);
}
Event::Suspended | Event::Resumed | Event::UserEvent(()) => {}
Event::WindowEvent {
window_id: _,
event,
} => {
Event::NewEvents(_) | Event::Suspended | Event::Resumed | Event::UserEvent(()) => {}
Event::WindowEvent { window_id, event } => {
if let Some(compositor) = &mut self.compositor {
self.window.handle_winit_window_event(
&self.constellation_sender,
compositor,
&event,
)
if let WindowEvent::CloseRequested = event {
// self.windows.remove(&window_id);
compositor.maybe_start_shutting_down();
} else {
let mut need_repaint = false;
for (id, window) in &mut self.windows {
if window_id == *id {
need_repaint = window.handle_winit_window_event(
&self.constellation_sender,
compositor,
&event,
);
}
}

if need_repaint {
compositor.repaint_synchronously(&mut self.windows);
}
}
}
}
e => log::warn!("Verso isn't supporting this event yet: {e:?}"),
e => log::trace!("Verso isn't supporting this event yet: {e:?}"),
}
}

/// Handle message came from Servo.
fn handle_servo_messages(&mut self) {
fn handle_servo_messages(&mut self, evl: &EventLoopWindowTarget<()>) {
let mut shutdown = false;
if let Some(compositor) = &mut self.compositor {
// Handle Compositor's messages first
log::trace!("Verso is handling Compositor messages");
if compositor.receive_messages(&mut self.window) {
if compositor.receive_messages(&mut self.windows) {
// And then handle Embedder messages
log::trace!(
"Verso is handling Embedder messages when shutdown state is set to {:?}",
compositor.shutdown_state
);
while let Some((top_level_browsing_context, msg)) =
self.embedder_receiver.try_recv_embedder_msg()
{
while let Some((webview_id, msg)) = self.embedder_receiver.try_recv_embedder_msg() {
match compositor.shutdown_state {
ShutdownState::NotShuttingDown => {
// TODO we need to worry about which window to handle message in
// multiwindow
self.window.handle_servo_message(
top_level_browsing_context,
msg,
&self.constellation_sender,
self.clipboard.as_mut(),
);
if let Some(id) = webview_id {
for window in self.windows.values_mut() {
if window.has_webview(id) {
if window.handle_servo_message(
id,
msg,
&self.constellation_sender,
self.clipboard.as_mut(),
compositor,
) {
let mut window =
Window::new_with_compositor(evl, compositor);
let panel_id = WebViewId::new();
let path = self.resource_dir.join("panel.html");
let url =
ServoUrl::from_file_path(path.to_str().unwrap())
.unwrap();
send_to_constellation(
&self.constellation_sender,
ConstellationMsg::NewWebView(url, panel_id),
);
let rect = DeviceIntRect::from_size(window.size());
window.panel = Some(WebView::new(panel_id, rect));
self.windows.insert(window.id(), window);
}
break;
}
}
} else {
// Handle message in Verso Window
log::trace!("Verso Window is handling Embedder message: {msg:?}");
match msg {
EmbedderMsg::SetCursor(cursor) => {
// TODO: This should move to compositor
if let Some(window) =
self.windows.get(&compositor.current_window)
{
window.set_cursor_icon(cursor);
}
}
EmbedderMsg::Shutdown | EmbedderMsg::ReadyToPresent(_) => {}
e => {
log::trace!("Verso Window isn't supporting handling this message yet: {e:?}")
}
}
}
}
ShutdownState::FinishedShuttingDown => {
log::error!("Verso shouldn't be handling messages after compositor has shut down");
68 changes: 44 additions & 24 deletions src/webview.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
use arboard::Clipboard;
use base::id::{PipelineId, PipelineNamespace, PipelineNamespaceId, WebViewId};
use base::id::{PipelineNamespace, PipelineNamespaceId, WebViewId};
use compositing_traits::ConstellationMsg;
use crossbeam_channel::Sender;
use embedder_traits::{CompositorEventVariant, EmbedderMsg, PromptDefinition};
use script_traits::TraversalDirection;
use servo_url::ServoUrl;
use webrender_api::units::DeviceIntRect;

use crate::{verso::send_to_constellation, window::Window};
use crate::{compositor::IOCompositor, verso::send_to_constellation, window::Window};

/// A web view is an area to display web browsing context. It's what user will treat as a "web page".
#[derive(Debug, Clone)]
pub struct WebView {
/// Webview ID
pub webview_id: WebViewId,
/// Pipeline ID for webrender usage.
pub pipeline_id: Option<PipelineId>,
/// The position and size of the webview.
pub rect: DeviceIntRect,
}

impl WebView {
/// Create a web view from Winit window.
pub fn new(webview_id: WebViewId, rect: DeviceIntRect) -> Self {
Self {
webview_id,
pipeline_id: None,
rect,
}
Self { webview_id, rect }
}

/// Create a panel view from Winit window. A panel is a special web view that focus on controlling states around window.
@@ -47,7 +41,6 @@ impl WebView {
let id = WebViewId::new();
Self {
webview_id: id,
pipeline_id: None,
rect,
}
}
@@ -61,19 +54,29 @@ impl Window {
message: EmbedderMsg,
sender: &Sender<ConstellationMsg>,
clipboard: Option<&mut Clipboard>,
compositor: &mut IOCompositor,
) {
log::trace!("Verso WebView {webview_id:?} is handling Embedder message: {message:?}",);
match message {
EmbedderMsg::LoadStart
| EmbedderMsg::HeadParsed
| EmbedderMsg::WebViewOpened(_)
| EmbedderMsg::WebViewClosed(_)
| EmbedderMsg::WebViewFocused(_) => {
| EmbedderMsg::WebViewClosed(_) => {
// Most WebView messages are ignored because it's done by compositor.
log::trace!("Verso WebView {webview_id:?} ignores this message: {message:?}")
}
EmbedderMsg::WebViewFocused(w) => {
log::debug!(
"Verso Window {:?}'s webview {} has loaded completely.",
self.id(),
w
);
compositor.set_webview_loaded(&w);
compositor.set_painting_order(self);
}
EmbedderMsg::LoadComplete => {
self.window.request_redraw();
send_to_constellation(sender, ConstellationMsg::FocusWebView(webview_id));
}
EmbedderMsg::AllowNavigationRequest(id, _url) => {
// TODO should provide a API for users to check url
@@ -114,35 +117,50 @@ impl Window {
}
}
e => {
log::warn!("Verso WebView isn't supporting this message yet: {e:?}")
log::trace!("Verso WebView isn't supporting this message yet: {e:?}")
}
}
}

/// Handle servo messages with main panel.
/// Handle servo messages with main panel. Return true it requests a new window.
pub fn handle_servo_messages_with_panel(
&mut self,
panel_id: WebViewId,
message: EmbedderMsg,
sender: &Sender<ConstellationMsg>,
clipboard: Option<&mut Clipboard>,
) {
compositor: &mut IOCompositor,
) -> bool {
log::trace!("Verso Panel {panel_id:?} is handling Embedder message: {message:?}",);
match message {
EmbedderMsg::LoadStart
| EmbedderMsg::HeadParsed
| EmbedderMsg::WebViewOpened(_)
| EmbedderMsg::WebViewClosed(_)
| EmbedderMsg::WebViewFocused(_) => {
| EmbedderMsg::WebViewClosed(_) => {
// Most WebView messages are ignored because it's done by compositor.
log::trace!("Verso Panel ignores this message: {message:?}")
}
EmbedderMsg::WebViewFocused(w) => {
log::debug!(
"Verso Window {:?}'s panel {} has loaded completely.",
self.id(),
w
);
compositor.set_webview_loaded(&w);
compositor.set_painting_order(self);
}
EmbedderMsg::LoadComplete => {
self.window.request_redraw();
// let demo_url = ServoUrl::parse("https://demo.versotile.org").unwrap();
let demo_url = ServoUrl::parse("https://keyboard-test.space").unwrap();
send_to_constellation(sender, ConstellationMsg::FocusWebView(panel_id));

let demo_url = ServoUrl::parse("https://example.com").unwrap();
let demo_id = WebViewId::new();
let size = self.size();
let mut rect = DeviceIntRect::from_size(size);
rect.min.y = rect.max.y.min(76);
self.webview = Some(WebView::new(demo_id, rect));
send_to_constellation(sender, ConstellationMsg::NewWebView(demo_url, demo_id));
log::debug!("Verso Window {:?} adds webview {}", self.id(), demo_id);
}
EmbedderMsg::AllowNavigationRequest(id, _url) => {
// The panel shouldn't navigate to other pages.
@@ -154,6 +172,7 @@ impl Window {
EmbedderMsg::Prompt(definition, _origin) => {
match definition {
PromptDefinition::Input(msg, _, prompt_sender) => {
let _ = prompt_sender.send(None);
if let Some(webview) = &self.webview {
let id = webview.webview_id;

@@ -185,7 +204,8 @@ impl Window {
// TODO Set EmbedderMsg::Status to None
}
"REFRESH" => {
send_to_constellation(sender, ConstellationMsg::Reload(id));
// send_to_constellation(sender, ConstellationMsg::Reload(id));
return true;
}
"MINIMIZE" => {
self.window.set_minimized(true);
@@ -194,15 +214,14 @@ impl Window {
let is_maximized = self.window.is_maximized();
self.window.set_maximized(!is_maximized);
}
e => log::warn!(
e => log::trace!(
"Verso Panel isn't supporting this prompt message yet: {e}"
),
}
}
}
let _ = prompt_sender.send(None);
}
_ => log::warn!("Verso Panel isn't supporting this prompt yet"),
_ => log::trace!("Verso Panel isn't supporting this prompt yet"),
}
}
EmbedderMsg::GetClipboardContents(sender) => {
@@ -226,8 +245,9 @@ impl Window {
});
}
e => {
log::warn!("Verso Panel isn't supporting this message yet: {e:?}")
log::trace!("Verso Panel isn't supporting this message yet: {e:?}")
}
}
false
}
}
256 changes: 155 additions & 101 deletions src/window.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use std::cell::Cell;

use base::id::{PipelineId, WebViewId};
use base::id::WebViewId;
use compositing_traits::ConstellationMsg;
use crossbeam_channel::Sender;
use embedder_traits::{Cursor, EmbedderMsg};
use euclid::{Point2D, Size2D};
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use script_traits::{TouchEventType, WheelDelta, WheelMode};
use surfman::{Connection, SurfaceType};
use surfman::Connection;
use surfman::SurfaceType;
use webrender_api::{
units::{
DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel, DevicePoint, LayoutVector2D,
@@ -18,8 +19,9 @@ use webrender_traits::RenderingContext;
use winit::{
dpi::PhysicalPosition,
event::{ElementState, TouchPhase, WindowEvent},
event_loop::EventLoopWindowTarget,
keyboard::ModifiersState,
window::{CursorIcon, Window as WinitWindow},
window::{CursorIcon, Window as WinitWindow, WindowBuilder, WindowId},
};

use crate::{
@@ -36,7 +38,7 @@ pub struct Window {
/// Access to Winit window
pub(crate) window: WinitWindow,
/// The main control panel of this window.
pub(crate) panel: WebView,
pub(crate) panel: Option<WebView>,
/// The WebView of this window.
pub(crate) webview: Option<WebView>,
/// The mouse physical position in the web view.
@@ -47,7 +49,19 @@ pub struct Window {

impl Window {
/// Create a Verso window from Winit window and return the rendering context.
pub fn new(window: WinitWindow) -> (Self, RenderingContext) {
pub fn new(evl: &EventLoopWindowTarget<()>) -> (Self, RenderingContext) {
let window = WindowBuilder::new()
// .with_transparent(true)
// .with_decorations(false)
.build(evl)
.expect("Failed to create window.");
#[cfg(macos)]
unsafe {
let rwh = window.raw_window_handle();
if let RawWindowHandle::AppKit(AppKitWindowHandle { ns_window, .. }) = rwh {
decorate_window(ns_window as *mut Object, LogicalPosition::new(8.0, 40.0));
}
}
let window_size = window.inner_size();
let window_size = Size2D::new(window_size.width as i32, window_size.height as i32);
let display_handle = window.raw_display_handle();
@@ -69,7 +83,7 @@ impl Window {
(
Self {
window,
panel: WebView::new_panel(DeviceIntRect::from_size(size)),
panel: Some(WebView::new_panel(DeviceIntRect::from_size(size))),
webview: None,
mouse_position: Cell::new(PhysicalPosition::default()),
modifiers_state: Cell::new(ModifiersState::default()),
@@ -78,20 +92,67 @@ impl Window {
)
}

/// Handle Winit window event.
/// Create a Verso window with the rendering context.
pub fn new_with_compositor(
evl: &EventLoopWindowTarget<()>,
compositor: &mut IOCompositor,
) -> Self {
let window = WindowBuilder::new()
// .with_transparent(true)
// .with_decorations(false)
.build(evl)
.expect("Failed to create window.");
#[cfg(macos)]
unsafe {
let rwh = window.raw_window_handle();
if let RawWindowHandle::AppKit(AppKitWindowHandle { ns_window, .. }) = rwh {
decorate_window(ns_window as *mut Object, LogicalPosition::new(8.0, 40.0));
}
}
let window_size = window.inner_size();
let window_size = Size2D::new(window_size.width as i32, window_size.height as i32);
let native_widget = compositor
.rendering_context
.connection()
.create_native_widget_from_raw_window_handle(window.raw_window_handle(), window_size)
.expect("Failed to create native widget");
let surface_type = SurfaceType::Widget { native_widget };
let surface = compositor
.rendering_context
.create_surface(surface_type)
.ok();
compositor.surfaces.insert(window.id(), surface);
Self {
window,
panel: None,
webview: None,
mouse_position: Cell::new(PhysicalPosition::default()),
modifiers_state: Cell::new(ModifiersState::default()),
}
}

/// Handle Winit window event and return a boolean to indicate if the compositor should repaint immediately.
pub fn handle_winit_window_event(
&mut self,
sender: &Sender<ConstellationMsg>,
compositor: &mut IOCompositor,
event: &winit::event::WindowEvent,
) {
) -> bool {
match event {
WindowEvent::RedrawRequested => {
compositor.present();
WindowEvent::Focused(focused) => {
if *focused {
compositor.swap_current_window(self);
}
}
WindowEvent::Resized(size) => {
let size = Size2D::new(size.width, size.height);
let _ = self.resize(size.to_i32(), compositor);
return self.resize(size.to_i32(), compositor);
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
compositor.on_scale_factor_event(*scale_factor as f32);
}
WindowEvent::CursorEntered { .. } => {
compositor.swap_current_window(self);
}
WindowEvent::CursorMoved { position, .. } => {
let cursor: DevicePoint = DevicePoint::new(position.x as f32, position.y as f32);
@@ -104,10 +165,10 @@ impl Window {
winit::event::MouseButton::Right => script_traits::MouseButton::Right,
winit::event::MouseButton::Middle => script_traits::MouseButton::Middle,
_ => {
log::warn!(
log::trace!(
"Verso Window isn't supporting this mouse button yet: {button:?}"
);
return;
return false;
}
};
let position = Point2D::new(
@@ -177,78 +238,70 @@ impl Window {
phase,
);
}
WindowEvent::CloseRequested => {
compositor.maybe_start_shutting_down();
}
WindowEvent::ModifiersChanged(modifier) => self.modifiers_state.set(modifier.state()),
WindowEvent::KeyboardInput { event, .. } => {
let event = keyboard_event_from_winit(&event, self.modifiers_state.get());
log::trace!("Verso is handling {:?}", event);
let msg = ConstellationMsg::Keyboard(event);
send_to_constellation(sender, msg);
}
e => log::warn!("Verso Window isn't supporting this window event yet: {e:?}"),
e => log::trace!("Verso Window isn't supporting this window event yet: {e:?}"),
}
false
}

/// Handle servo messages.
/// Handle servo messages. Return true if it requests a new window
pub fn handle_servo_message(
&mut self,
webview_id: Option<WebViewId>,
webview_id: WebViewId,
message: EmbedderMsg,
sender: &Sender<ConstellationMsg>,
clipboard: Option<&mut Clipboard>,
) {
match webview_id {
// // Handle message in Verso Panel
Some(p) if p == self.panel.webview_id => {
self.handle_servo_messages_with_panel(p, message, sender, clipboard);
}
// Handle message in Verso WebView
Some(w) => {
self.handle_servo_messages_with_webview(w, message, sender, clipboard);
}
// Handle message in Verso Window
None => {
log::trace!("Verso Window is handling Embedder message: {message:?}");
match message {
EmbedderMsg::ReadyToPresent(_w) => {
self.window.request_redraw();
}
EmbedderMsg::SetCursor(cursor) => {
self.set_cursor_icon(cursor);
}
EmbedderMsg::Shutdown => {}
e => {
log::warn!("Verso Window isn't supporting handling this message yet: {e:?}")
}
}
compositor: &mut IOCompositor,
) -> bool {
// // Handle message in Verso Panel
if let Some(panel) = &self.panel {
if panel.webview_id == webview_id {
return self.handle_servo_messages_with_panel(
webview_id, message, sender, clipboard, compositor,
);
}
}
// Handle message in Verso WebView
self.handle_servo_messages_with_webview(webview_id, message, sender, clipboard, compositor);
false
}

/// Queues a Winit `WindowEvent::RedrawRequested` event to be emitted that aligns with the windowing system drawing loop.
pub fn request_redraw(&self) {
self.window.request_redraw()
}

/// Resize the rendering context and all web views.
pub fn resize(&mut self, size: Size2D<i32, DevicePixel>, compositor: &mut IOCompositor) {
/// Resize the rendering context and all web views. Return true if the compositor should repaint and present
/// after this.
pub fn resize(
&mut self,
size: Size2D<i32, DevicePixel>,
compositor: &mut IOCompositor,
) -> bool {
let need_resize = compositor.on_resize_window_event(size);

let rect = DeviceIntRect::from_size(size);
compositor.on_resize_webview_event(self.panel.webview_id, rect);
if let Some(panel) = &mut self.panel {
let rect = DeviceIntRect::from_size(size);
panel.rect = rect;
compositor.on_resize_webview_event(panel.webview_id, rect);
}

if let Some(w) = &self.webview {
if let Some(w) = &mut self.webview {
let mut rect = DeviceIntRect::from_size(size);
rect.min.y = rect.max.y.min(76);
w.rect = rect;
compositor.on_resize_webview_event(w.webview_id, rect);
}

if need_resize {
compositor.repaint_synchronously(self);
compositor.present();
}
compositor.set_painting_order(self);

need_resize
}

/// Size of the window that's used by webrender.
@@ -257,51 +310,20 @@ impl Window {
Size2D::new(size.width as i32, size.height as i32)
}

/// Get Winit window ID of the window.
pub fn id(&self) -> WindowId {
self.window.id()
}

/// Scale factor of the window. This is also known as HIDPI.
pub fn scale_factor(&self) -> f64 {
self.window.scale_factor()
}

/// Get the mutable reference of the webview in this window from provided webview ID.
pub fn get_webview(&mut self, id: WebViewId) -> Option<&mut WebView> {
if self.panel.webview_id == id {
Some(&mut self.panel)
} else {
self.webview.as_mut().filter(|w| w.webview_id == id)
}
}

/// Set the webview to this window. It won't be updated if the exisitng webview and pipeline ID
/// are the same. This will also set the painting order of the compositor and tell
/// constellation to focus the webview.
pub fn set_webview(
&mut self,
webview_id: WebViewId,
pipline_id: PipelineId,
compositor: &mut IOCompositor,
) {
if self.panel.webview_id == webview_id {
if self.panel.pipeline_id != Some(pipline_id) {
self.panel.pipeline_id = Some(pipline_id);
}
} else if let Some(webview) = &mut self.webview {
if webview.webview_id == webview_id && webview.pipeline_id != Some(pipline_id) {
webview.pipeline_id = Some(pipline_id);
}
} else {
let size = self.size();
let mut rect = DeviceIntRect::from_size(size);
rect.min.y = rect.max.y.min(76);
self.webview = Some(WebView::new(webview_id, rect));
}

compositor.set_painting_order(self.paiting_order());
self.resize(self.size(), compositor);

send_to_constellation(
&compositor.constellation_chan,
ConstellationMsg::FocusWebView(webview_id),
);
/// Check if the window has such webview.
pub fn has_webview(&self, id: WebViewId) -> bool {
self.panel.as_ref().map_or(false, |w| w.webview_id == id)
|| self.webview.as_ref().map_or(false, |w| w.webview_id == id)
}

/// Remove the webview in this window by provided webview ID. If this is the panel, it will
@@ -310,29 +332,36 @@ impl Window {
&mut self,
id: WebViewId,
compositor: &mut IOCompositor,
) -> Option<WebView> {
if id == self.panel.webview_id {
compositor.maybe_start_shutting_down();
None
) -> (Option<WebView>, bool) {
if self.panel.as_ref().filter(|w| w.webview_id == id).is_some() {
self.webview.as_ref().map(|w| {
send_to_constellation(
&compositor.constellation_chan,
ConstellationMsg::CloseWebView(w.webview_id),
)
});
(self.panel.take(), false)
} else if self
.webview
.as_ref()
.filter(|w| w.webview_id == id)
.is_some()
{
self.webview.take()
(self.webview.take(), self.panel.is_none())
} else {
None
(None, false)
}
}

/// Get the painting order of this window.
pub fn paiting_order(&self) -> Vec<WebView> {
pub fn painting_order(&self) -> Vec<WebView> {
let mut order = vec![];
if let Some(webview) = &self.webview {
order.push(webview.clone());
}
order.push(self.panel.clone());
if let Some(panel) = &self.panel {
order.push(panel.clone());
}
order
}

@@ -378,3 +407,28 @@ impl Window {
self.window.set_cursor_icon(winit_cursor);
}
}

/* window decoration */
#[cfg(macos)]
use cocoa::appkit::{NSWindow, NSWindowStyleMask, NSWindowTitleVisibility};
#[cfg(macos)]
use objc::runtime::Object;
#[cfg(macos)]
use raw_window_handle::{AppKitWindowHandle, RawWindowHandle};
#[cfg(macos)]
use winit::dpi::LogicalPosition;

/// Window decoration for macOS.
#[cfg(macos)]
pub unsafe fn decorate_window(window: *mut Object, _position: LogicalPosition<f64>) {
NSWindow::setTitlebarAppearsTransparent_(window, cocoa::base::YES);
NSWindow::setTitleVisibility_(window, NSWindowTitleVisibility::NSWindowTitleHidden);
NSWindow::setStyleMask_(
window,
NSWindowStyleMask::NSTitledWindowMask
| NSWindowStyleMask::NSFullSizeContentViewWindowMask
| NSWindowStyleMask::NSClosableWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask,
);
}

0 comments on commit 07a88a8

Please sign in to comment.